/* * Copyright (c) 2011, IETR/INSA of Rennes * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the IETR/INSA of Rennes nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ package net.sf.orcc.cal.validation; import static net.sf.orcc.cal.cal.CalPackage.eINSTANCE; import java.util.List; import net.sf.orcc.cal.CalDiagnostic; import net.sf.orcc.cal.cal.AstActor; import net.sf.orcc.cal.cal.AstEntity; import net.sf.orcc.cal.cal.AstProcedure; import net.sf.orcc.cal.cal.AstUnit; import net.sf.orcc.cal.cal.ExpressionCall; import net.sf.orcc.cal.cal.Function; import net.sf.orcc.cal.cal.Generator; import net.sf.orcc.cal.cal.Import; import net.sf.orcc.cal.cal.InputPattern; import net.sf.orcc.cal.cal.StatementAssign; import net.sf.orcc.cal.cal.StatementCall; import net.sf.orcc.cal.cal.StatementForeach; import net.sf.orcc.cal.cal.Variable; import net.sf.orcc.cal.cal.VariableReference; import net.sf.orcc.cal.services.Typer; import net.sf.orcc.cal.util.BooleanSwitch; import net.sf.orcc.cal.util.Util; import net.sf.orcc.util.OrccUtil; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import com.google.inject.Inject; /** * This class describes a validator that computes warnings for an RVC-CAL * actor/unit. * * @author Matthieu Wipliez * */ public class WarningValidator extends AbstractCalValidator { @Inject XtextResourceSet rs; @Check(CheckType.NORMAL) public void checkAstProcedure(final AstProcedure procedure) { boolean used = new BooleanSwitch() { @Override public Boolean caseStatementCall(StatementCall call) { if (call.getProcedure().equals(procedure)) { return true; } return false; } }.doSwitch(EcoreUtil.getRootContainer(procedure)); if (!used && procedure.eContainer() instanceof AstActor && !Util.hasAnnotation("native", procedure.getAnnotations())) { warning("The procedure " + procedure.getName() + " is never called", procedure, eINSTANCE.getAstProcedure_Name(), -1); } } @Check(CheckType.NORMAL) public void checkFunction(final Function function) { // do not check functions of a unit if (function.eContainer() instanceof AstUnit) { return; } boolean used = new BooleanSwitch() { @Override public Boolean caseExpressionCall(ExpressionCall expression) { if (expression.getFunction().equals(function)) { return true; } return super.caseExpressionCall(expression); } }.doSwitch(EcoreUtil.getRootContainer(function)); if (!used && !Util.hasAnnotation("native", function.getAnnotations())) { warning("The function " + function.getName() + " is never called", function, eINSTANCE.getFunction_Name(), -1); } } @Check(CheckType.NORMAL) public void checkImport(Import theImport) { IWorkspace workspace; try { // get the file (we know it's a file) workspace = ResourcesPlugin.getWorkspace(); } catch (IllegalStateException e) { // This validation step is executed without a workspace open. This // is normal if the validation is performed from JUnit tests in full // headless environment (i.e. Without opening the second eclipse and // without any GUI). In that case, we catch the exception and stop // this method. Doing this, all others validations will be performed // as expected. return; } final IWorkspaceRoot wsRoot = workspace.getRoot(); final String importString = theImport.getImportedNamespace(); final int lastDotOffset = importString.lastIndexOf('.'); if (lastDotOffset == -1) { error("Malformed import", eINSTANCE.getImport_ImportedNamespace()); return; } final String resourceQName = importString.substring(0, lastDotOffset); final IPath resourcePath = new Path(resourceQName.replace('.', '/')) .addFileExtension(OrccUtil.CAL_SUFFIX); final IProject project = Util.getProject(theImport); final List<IFolder> srcFolders = OrccUtil.getAllSourceFolders(project); for (final IFolder folder : srcFolders) { final IPath resourceFullPath = folder.getFullPath().append( resourcePath); if (wsRoot.exists(resourceFullPath)) { final String importedElement = importString .substring(lastDotOffset + 1); checkImportedElement(resourceFullPath, importedElement); return; } } warning(resourceQName + " not found in the current project " + "and its dependencies", eINSTANCE.getImport_ImportedNamespace()); } private void checkImportedElement(final IPath path, final String element) { // Check if imported entity is a valid resource final Resource resource = rs.getResource( URI.createPlatformResourceURI(path.toString(), true), true); if (resource == null || resource.getContents().size() == 0) { warning(path.toString() + " is not a valid resource", eINSTANCE.getImport_ImportedNamespace()); return; } // Check if imported entity is a unit final EObject eObject = resource.getContents().get(0); if (!(eObject instanceof AstEntity) || ((AstEntity) eObject).getUnit() == null) { warning(path.toString() + " is not a Unit", eINSTANCE.getImport_ImportedNamespace()); return; } // Imports ending with '*' are always considered OK (everything in the // unit is imported) if ("*".equals(element)) { return; } // Check imported element final AstUnit unit = ((AstEntity) eObject).getUnit(); for (final Function function : unit.getFunctions()) { if (function.getName().equals(element)) { return; } } for (final AstProcedure procedure : unit.getProcedures()) { if (procedure.getName().equals(element)) { return; } } for (final Variable variable : unit.getVariables()) { if (variable.getName().equals(element)) { return; } } warning(element + " not found in " + path.toString(), eINSTANCE.getImport_ImportedNamespace()); } /** * Checks that the given variable is used. If it is not, issue a warning. * * @param variable * a variable */ private void checkIsVariableUsed(final Variable variable) { EObject container = variable.eContainer(); if (container instanceof InputPattern || container instanceof Generator || container instanceof StatementForeach) { // do not warn about these variables because it is ok if they are // not used return; } else if (container instanceof AstUnit) { // variables in unit, too, because they may be used in other // entities return; } else if (container instanceof Function) { Function function = (Function) variable.eContainer(); if (Util.hasAnnotation("native", function.getAnnotations())) { // parameters in native functions are not read in CAL return; } } else if (container instanceof AstProcedure) { AstProcedure procedure = (AstProcedure) variable.eContainer(); if (Util.hasAnnotation("native", procedure.getAnnotations())) { // parameters in native procedures are not read in CAL return; } } EReference reference = variable.eContainmentFeature(); AstEntity entity = EcoreUtil2.getContainerOfType(variable, AstEntity.class); if (reference == eINSTANCE.getAstActor_Parameters() && Util.hasAnnotation("native", entity.getAnnotations())) { // parameters in native actors are not read in CAL return; } boolean isRead = new BooleanSwitch() { @Override public Boolean caseVariableReference(VariableReference ref) { return ref.getVariable().equals(variable); } }.doSwitch(entity); boolean isWritten = new BooleanSwitch() { @Override public Boolean caseStatementAssign(StatementAssign assign) { return assign.getTarget().getVariable().equals(variable); } }.doSwitch(entity); // do not warn about unused actor parameters // used for system actors if (!isRead && !isWritten) { warning("The variable " + variable.getName() + " is never used", variable, eINSTANCE.getVariable_Name(), CalDiagnostic.WARNING_UNUSED); } else if (!isRead) { if (reference == eINSTANCE.getAstProcedure_Parameters() && Typer.getType(variable).isList()) { return; } warning("The variable " + variable.getName() + " is never read", variable, eINSTANCE.getVariable_Name(), CalDiagnostic.WARNING_UNUSED); } else if (!isWritten) { if (!Util.isAssignable(variable) || reference == eINSTANCE.getFunction_Parameters() || reference == eINSTANCE.getAstProcedure_Parameters()) { return; } // warning("The variable " + variable.getName() + // " is never written", variable, eINSTANCE.getVariable_Name(), -1); } } @Check(CheckType.NORMAL) public void checkVariable(Variable variable) { checkIsVariableUsed(variable); } }