Skip to content
This repository has been archived by the owner on Jan 7, 2020. It is now read-only.

Commit

Permalink
cyclic method call detection part I
Browse files Browse the repository at this point in the history
  • Loading branch information
andresteingress committed Mar 7, 2011
1 parent 2c03633 commit 788a5f6
Show file tree
Hide file tree
Showing 11 changed files with 504 additions and 36 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ allprojects {
subprojects {
apply plugin: "groovy"

compileGroovy.options.fork = false
compileGroovy.options.fork = true
compileTestGroovy.options.fork = true

dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2011, Andre Steingress
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
* 2.) 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.
* 3.) Neither the name of Andre Steingress 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 HOLDER 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 org.gcontracts;

/**
* Thrown whenever pre- or post-conditions are called in a cyclic way.
*
* @author ast
*/
public class CyclicAssertionCallException extends RuntimeException {

public CyclicAssertionCallException() {
}

public CyclicAssertionCallException(String s) {
super(s);
}

public CyclicAssertionCallException(String s, Throwable throwable) {
super(s, throwable);
}

public CyclicAssertionCallException(Throwable throwable) {
super(throwable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@
*/
package org.gcontracts.generation;

import groovy.lang.ProxyMetaClass;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.TryCatchStatement;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.classgen.*;
import org.codehaus.groovy.control.io.ReaderSource;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
Expand All @@ -36,6 +35,8 @@
import org.gcontracts.util.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
Expand Down Expand Up @@ -73,22 +74,35 @@ public static MethodNode getInvariantMethodNode(final ClassNode classNode) {
return classNode.getDeclaredMethod(getInvariantMethodName(classNode), Parameter.EMPTY_ARRAY);
}

protected BlockStatement wrapAssertionBooleanExpression(BooleanExpression classInvariantExpression) {
protected BlockStatement wrapAssertionBooleanExpression(ClassNode type, MethodNode methodNode, BooleanExpression classInvariantExpression) {
final BlockStatement assertBlockStatement = new BlockStatement();
final ClassNode violationTrackerClassNode = ClassHelper.makeWithoutCaching(ViolationTracker.class);

final String $_gc_caci = "$_gc_caci";
final String $_gc_proxy = "$_gc_proxy";

final VariableExpression $_gc_result = new VariableExpression("$_gc_result", ClassHelper.boolean_TYPE);
final VariableExpression caciVariableExpression = new VariableExpression($_gc_caci, ClassHelper.makeWithoutCaching(CyclicAssertionCallInterceptor.class));
final VariableExpression proxyVariableExpression = new VariableExpression($_gc_proxy, ClassHelper.DYNAMIC_TYPE);

assertBlockStatement.addStatement(new ExpressionStatement(new DeclarationExpression($_gc_result, Token.newSymbol(Types.ASSIGN, -1, -1), ConstantExpression.FALSE)));
assertBlockStatement.addStatement(new ExpressionStatement(new DeclarationExpression(caciVariableExpression, Token.newSymbol(Types.ASSIGN, -1, -1), new ConstructorCallExpression(ClassHelper.makeWithoutCaching(CyclicAssertionCallInterceptor.class), ArgumentListExpression.EMPTY_ARGUMENTS))));
assertBlockStatement.addStatement(new ExpressionStatement(new DeclarationExpression(proxyVariableExpression, Token.newSymbol(Types.ASSIGN, -1, -1), new MethodCallExpression(new ClassExpression(ClassHelper.makeWithoutCaching(CyclicMethodCallAwareMetaClass.class)), "getInstance", new ClassExpression(type)))));
assertBlockStatement.addStatement(new ExpressionStatement(new MethodCallExpression(proxyVariableExpression, "setInterceptor", caciVariableExpression)));

assertBlockStatement.addStatement(new ExpressionStatement(
new MethodCallExpression(new ClassExpression(violationTrackerClassNode), "init", ArgumentListExpression.EMPTY_ARGUMENTS))
);

final VariableExpression $_gc_result = new VariableExpression("$_gc_result", ClassHelper.boolean_TYPE);
assertBlockStatement.addStatement(new ExpressionStatement(new DeclarationExpression($_gc_result,
assertBlockStatement.addStatement(new ExpressionStatement(new MethodCallExpression(proxyVariableExpression, "init", ArgumentListExpression.EMPTY_ARGUMENTS)));
assertBlockStatement.addStatement(new ExpressionStatement(new BinaryExpression($_gc_result,
Token.newSymbol(Types.ASSIGN, -1, -1),
classInvariantExpression
)));

BlockStatement finallyBlockStatement = new BlockStatement();
finallyBlockStatement.addStatement(new ExpressionStatement(new MethodCallExpression(new ClassExpression(violationTrackerClassNode), "deinit", ArgumentListExpression.EMPTY_ARGUMENTS)));
finallyBlockStatement.addStatement(new ExpressionStatement(new MethodCallExpression(proxyVariableExpression, "deinit", ArgumentListExpression.EMPTY_ARGUMENTS)));

assertBlockStatement.addStatement(
new IfStatement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ public void generateInvariantAssertionStatement(final ClassNode type, final Bool

BooleanExpression classInvariantExpression = addCallsToSuperAnnotationClosure(type, ClassInvariant.class, classInvariant);

final BlockStatement blockStatement = wrapAssertionBooleanExpression(classInvariantExpression);
blockStatement.addStatement(new ReturnStatement(ConstantExpression.TRUE));
final BlockStatement blockStatement = new BlockStatement();

// add a local protected method with the invariant closure - this is needed for invariant checks in inheritance lines
type.addMethod(getInvariantMethodName(type), Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC, ClassHelper.Boolean_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, blockStatement);
MethodNode methodNode = type.addMethod(getInvariantMethodName(type), Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC, ClassHelper.Boolean_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, blockStatement);
blockStatement.addStatements(wrapAssertionBooleanExpression(type, methodNode, classInvariantExpression).getStatements());
blockStatement.addStatement(new ReturnStatement(ConstantExpression.TRUE));
}

private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, final Class<? extends Annotation> annotationType, BooleanExpression booleanExpression) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) 2011, Andre Steingress
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
* 2.) 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.
* 3.) Neither the name of Andre Steingress 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 HOLDER 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 org.gcontracts.generation;

import groovy.lang.Interceptor;
import org.gcontracts.CyclicAssertionCallException;

import java.util.LinkedList;
import java.util.List;

/**
* {@link Interceptor} implementation which checks for cyclic method calls - as these need to be avoided
* in contract definitions.
*
* @see Interceptor
*
* @author ast
*/
public class CyclicAssertionCallInterceptor implements Interceptor {

private List<String> callStack = new LinkedList<String>();

@Override
public Object beforeInvoke(Object object, String methodName, Object[] arguments) {
if (callStack.contains(methodName)) throw new CyclicAssertionCallException("Method '" + methodName + "' has already been called from the current assertion - assertion call cycle detected!");
callStack.add(methodName);
System.out.println("ADDED " + methodName);
return object;
}

@Override
public Object afterInvoke(Object object, String methodName, Object[] arguments, Object result) {
callStack.remove(methodName);
System.out.println("REMOVED " + methodName);
return result;
}

@Override
public boolean doInvoke() {
return true;
}
}
Loading

0 comments on commit 788a5f6

Please sign in to comment.