/* * JBoss, Home of Professional Open Source * Copyright 2009-10 Red Hat and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * * @authors Andrew Dinn */ package org.jboss.byteman.agent; import org.objectweb.asm.ClassVisitor; import org.jboss.byteman.rule.type.TypeHelper; import org.jboss.byteman.agent.adapter.*; /** * Specifies a location in a method at which a rule trigger should be inserted */ public abstract class Location { /** * create a location object of a given type * @param type the type of location being specified * @param parameters the text of the parameters appended to the location specifier * @return a location of the appropriate type or null if the parameters are incorrectly specified */ public static Location create(LocationType type, String parameters) { switch (type) { case ENTRY: return EntryLocation.create(parameters); case LINE: return LineLocation.create(parameters); case READ: return AccessLocation.create(parameters, ACCESS_READ, false); case READ_COMPLETED: return AccessLocation.create(parameters, ACCESS_READ, true); case WRITE: return AccessLocation.create(parameters, ACCESS_WRITE, false); case WRITE_COMPLETED: return AccessLocation.create(parameters, ACCESS_WRITE, true); case INVOKE: return InvokeLocation.create(parameters, false); case INVOKE_COMPLETED: return InvokeLocation.create(parameters, true); case SYNCHRONIZE: return SynchronizeLocation.create(parameters, false); case SYNCHRONIZE_COMPLETED: return SynchronizeLocation.create(parameters, true); case THROW: return ThrowLocation.create(parameters); case EXIT: return ExitLocation.create(parameters); case EXCEPTION_EXIT: return ExceptionExitLocation.create(parameters); } return null; } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @param cv the current class visitor * @param transformContext the current transform context * @return the required adapter */ public abstract RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext); /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @param cv the current class visitor * @param transformContext the current transform context * @return the required adapter */ public abstract RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext); /** * identify the type of this location * @return the type of this location */ public abstract LocationType getLocationType(); /** * flag indicating that a field access location refers to field READ operations */ public static final int ACCESS_READ = 1; /** * flag indicating that a field access location refers to field WRITE operations */ public static final int ACCESS_WRITE = 2; /** * location identifying a method entry trigger point */ private static class EntryLocation extends Location { /** * create a location identifying a method entry trigger point * @param parameters the text of the parameters appended to the location specifier * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters) { if (!parameters.trim().equals("")) { // hmm, not expecting any parameters here return null; } return new EntryLocation(); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @param cv the current class visitor * @param transformContext the current transform context * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { return new EntryCheckAdapter(cv, transformContext); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @param cv the current class visitor * @param transformContext the current transform context * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { return new EntryTriggerAdapter(cv, transformContext); } public LocationType getLocationType() { return LocationType.ENTRY; } public String toString() { return "AT ENTRY"; } } /** * location identifying a method line trigger point */ private static class LineLocation extends Location { /** * the line at which the trigger point should be inserted */ private int targetLine; /** * construct a location identifying a method line trigger point * @param targetLine the line at which the trigger point should be inserted */ private LineLocation(int targetLine) { this.targetLine = targetLine; } /** * create a location identifying a method entry trigger point * @param parameters the text of the parameters appended to the location specifier * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters) { try { int targetLine = Integer.decode(parameters.trim()); return new LineLocation(targetLine); } catch (NumberFormatException nfe) { return null; } } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @param cv the current class visitor * @param transformContext the current transform context * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { return new LineCheckAdapter(cv, transformContext, targetLine); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @param cv the current class visitor * @param transformContext the current transform context * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { return new LineTriggerAdapter(cv, transformContext, targetLine); } public LocationType getLocationType() { return LocationType.LINE; } public String toString() { return "AT LINE " + targetLine; } } /** * location identifying a generic access trigger point */ private static abstract class AccessLocation extends Location { /** * count identifying which access should be taken as the trigger point. if not specified * as a parameter this defaults to the first access. */ protected int count; /** * flags identifying which type of access should be used to identify the trigger. this is either * ACCESS_READ, ACCESS_WRITE or an OR of these two values */ protected int flags; /** * flag which is false if the trigger should be inserted before the field access is performed * and true if it should be inserted after */ protected boolean whenComplete; protected AccessLocation(int count, int flags, boolean whenComplete) { this.count = count; this.flags = flags; this.whenComplete = whenComplete; } /** * create a location identifying a method entry trigger point * @param parameters the text of the parameters appended to the location specifier * @param flags bit field comprising one or other of flags ACCESS_READ and ACCESS_WRITE identifying * whether this specifies field READ or WRITE operations * @param whenComplete false if the trigger should be inserted before the access is performed * and true if it should be inserted after * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters, int flags, boolean whenComplete) { String text = parameters.trim(); int count; // check for trailing count if (text.contains(" ")) { int spaceIdx = text.lastIndexOf(" "); String countText = text.substring(spaceIdx).trim(); if (countText.equals("ALL")) { // a zero count means match all count = 0; } else { try { count = Integer.valueOf(countText); } catch (NumberFormatException nfe) { return null; } } text = text.substring(0, spaceIdx).trim(); } else { count = 1; } if (text.equals("")) { return null; } // check for a local or parameter var name identified by a leading $ if (text.startsWith("$")) { String varname = text.substring(1).trim(); return new VariableAccessLocation(varname, count, flags, whenComplete); } else { String typeName; String fieldName; // check for leading type name if (text.contains(".")) { int dotIdx = text.lastIndexOf("."); typeName = text.substring(0, dotIdx).trim(); fieldName=text.substring(dotIdx + 1).trim(); } else { typeName = null; fieldName = text; } // TODO sanity check type and field name return new FieldAccessLocation(typeName, fieldName, count, flags, whenComplete); } } public LocationType getLocationType() { if ((flags & ACCESS_WRITE) != 0) { if (whenComplete) { return LocationType.WRITE_COMPLETED; } else { return LocationType.WRITE; } } else { if (whenComplete) { return LocationType.READ_COMPLETED; } else { return LocationType.READ; } } } } /** * location identifying a field access trigger point */ private static class FieldAccessLocation extends AccessLocation { /** * the name of the field being accessed at the point where the trigger point should be inserted */ private String fieldName; /** * the name of the type to which the field belongs or null if any type will do */ private String typeName; /** * construct a location identifying a field read trigger point * @param typeName the name of the class owning the field * @param fieldName the name of the field being read * @param count count identifying which access should be taken as the trigger point * @param flags bit field comprising one or other of flags ACCESS_READ and ACCESS_WRITE identifying * whether this specifies field READ or WRITE operations * @param whenComplete false if the trigger should be inserted before the access is performed * and true if it should be inserted after */ private FieldAccessLocation(String typeName, String fieldName, int count, int flags, boolean whenComplete) { super(count, flags, whenComplete); this.typeName = typeName; this.fieldName = fieldName; } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { return new FieldAccessCheckAdapter(cv, transformContext, typeName, fieldName, flags, count); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { return new FieldAccessTriggerAdapter(cv, transformContext, typeName, fieldName, flags, count, whenComplete); } public String toString() { String text; if (whenComplete) { text = "AFTER "; } else { text = "AT "; } if (flags == ACCESS_READ) { text += "READ "; } else if (flags == ACCESS_WRITE) { text += "WRITE "; } else { text += "ACCESS "; } if (typeName != null) { text += typeName + "."; } text += fieldName; if (count != 1) { if (count == 0) { text += " ALL"; } else { text += " " + count; } } return text; } } /** * location identifying a variable access trigger point */ private static class VariableAccessLocation extends AccessLocation { /** * the name of the variable being accessed at the point where the trigger point should be inserted */ private String variableName; /** * flag which is true if the name is a method parameter index such as $0, $1 etc otherwise false */ private boolean isIndex; /** * construct a location identifying a variable read trigger point * @param variablename the name of the variable being read * @param count count identifying which access should be taken as the trigger point * @param flags bit field comprising one or other of flags ACCESS_READ and ACCESS_WRITE identifying * whether this specifies field READ or WRITE operations * @param whenComplete false if the trigger should be inserted before the access is performed * and true if it should be inserted after */ protected VariableAccessLocation(String variablename, int count, int flags, boolean whenComplete) { super(count, flags, whenComplete); this.variableName = variablename; isIndex = variablename.matches("[0-9]+"); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { if (isIndex) { int paramIdx = Integer.valueOf(variableName); return new IndexParamAccessCheckAdapter(cv, transformContext, paramIdx, flags, count); } else { // we need to insert a BMJSRInliner into the pipeline so that the check adapter gets // notified when local vars go in and out of scope return new VariableAccessCheckAdapter(cv, transformContext, variableName, flags, count); } } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { if (isIndex) { int paramIdx = Integer.valueOf(variableName); return new IndexParamAccessTriggerAdapter(cv, transformContext, paramIdx, flags, count, whenComplete); } else { return new VariableAccessTriggerAdapter(cv, transformContext, variableName, flags, count, whenComplete); } } public LocationType getLocationType() { if ((flags & ACCESS_WRITE) != 0) { if (whenComplete) { return LocationType.WRITE_COMPLETED; } else { return LocationType.WRITE; } } else { if (whenComplete) { return LocationType.READ_COMPLETED; } else { return LocationType.READ; } } } public String toString() { String text; if (whenComplete) { text = "AFTER "; } else { text = "AT "; } if (flags == ACCESS_READ) { text += "READ "; } else if (flags == ACCESS_WRITE) { text += "WRITE "; } else { text += "ACCESS "; } text += "$"; text += variableName; if (count != 1) { if (count == 0) { text += " ALL"; } else { text += " " + count; } } return text; } } /** * location identifying a method invocation trigger point */ private static class InvokeLocation extends Location { /** * the name of the method being invoked at the point where the trigger point should be inserted */ private String methodName; /** * the name of the type to which the method belongs or null if any type will do */ private String typeName; /** * the method signature in externalised form */ private String signature; /** * count identifying which invocation should be taken as the trigger point. if not specified * as a parameter this defaults to the first invocation. */ private int count; /** * flag which is false if the trigger should be inserted before the method invocation is performed * and true if it should be inserted after */ private boolean whenComplete; /** * construct a location identifying a method invocation trigger point * @param typeName the name of the class owning the method * @param methodName the name of the method being called * @param signature the method signature in externalised form * @param count count identifying which invocation should be taken as the trigger point * @param whenComplete false if the trigger should be inserted before the method invocation is * performed and true if it should be inserted after */ private InvokeLocation(String typeName, String methodName, String signature, int count, boolean whenComplete) { this.typeName = typeName; this.methodName = methodName; this.signature = signature; this.count = count; this.whenComplete = whenComplete; } /** * create a location identifying a method entry trigger point * @param parameters the text of the parameters appended to the location specifier * @param whenComplete false if the trigger should be inserted before the access is performed * and true if ti shoudl be inserted after * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters, boolean whenComplete) { String text = parameters.trim(); String typeName; String fieldName; String signature; int count; // check for trailing count if (text.contains(")")) { int tailIdx = text.lastIndexOf(")"); String countText = text.substring(tailIdx + 1).trim(); if (!countText.equals("")) { if (countText.equals("ALL")) { // a zero count means all count = 0; } else { try { count = Integer.valueOf(countText); } catch (NumberFormatException nfe) { return null; } } } else { count = 1; } text = text.substring(0, tailIdx + 1).trim(); } else if (text.contains(" ")) { int tailIdx = text.lastIndexOf(" "); String countText = text.substring(tailIdx + 1).trim(); if (!countText.equals("")) { if (countText.equals("ALL")) { // a zero count means all count = 0; } else { try { count = Integer.valueOf(countText); } catch (NumberFormatException nfe) { return null; } } } else { count = 1; } text = text.substring(0, tailIdx).trim(); } else { count = 1; } // check for argument list if (text.contains("(")) { signature = TypeHelper.parseMethodDescriptor(text); text=TypeHelper.parseMethodName(text); } else { signature = ""; } // check for leading type name if (text.contains(".")) { int dotIdx = text.lastIndexOf("."); typeName = text.substring(0, dotIdx).trim(); fieldName=text.substring(dotIdx + 1).trim(); } else { typeName = null; fieldName = text; } // TODO sanity check type and field name return new InvokeLocation(typeName, fieldName, signature, count, whenComplete); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { return new InvokeCheckAdapter(cv, transformContext, typeName, methodName, signature, count); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { return new InvokeTriggerAdapter(cv, transformContext, typeName, methodName, signature, count, whenComplete); } public LocationType getLocationType() { if (whenComplete) { return LocationType.INVOKE_COMPLETED; } else { return LocationType.INVOKE; } } public String toString() { String text; if (whenComplete) { text = "AFTER INVOKE "; } else { text = "AT INVOKE "; } if (typeName != null) { text += typeName + "."; } text += methodName; if (signature.length() > 0) { text = text + TypeHelper.internalizeDescriptor(signature); } if (count != 1) { if (count == 0) { text += " ALL"; } else { text += " " + count; } } return text; } } /** * location identifying a synchronization trigger point */ private static class SynchronizeLocation extends Location { /** * count identifying which synchronization should be taken as the trigger point. if not specified * as a parameter this defaults to the first synchronization. */ private int count; /** * flag which is false if the trigger should be inserted before the synchronization is performed * and true if it should be inserted after */ private boolean whenComplete; /** * construct a location identifying a synchronization trigger point * @param count count identifying which synchronization should be taken as the trigger point * @param whenComplete false if the trigger should be inserted before the synchronization is * performed and true if it should be inserted after */ private SynchronizeLocation(int count, boolean whenComplete) { this.count = count; this.whenComplete = whenComplete; } /** * create a location identifying a synchronization trigger point * @param parameters the text of the parameters appended to the location specifier * @param whenComplete false if the trigger should be inserted before the synchronization is * performed and true if it should be inserted after * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters, boolean whenComplete) { String text = parameters.trim(); int count; // check for count if (text.length() != 0) { if (text.equals("ALL")) { // a zero count means all count = 0; } else { try { count = Integer.valueOf(text); } catch (NumberFormatException nfe) { return null; } } } else { count = 1; } return new SynchronizeLocation(count, whenComplete); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { return new SynchronizeCheckAdapter(cv, transformContext, count); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { return new SynchronizeTriggerAdapter(cv, transformContext, count, whenComplete); } public LocationType getLocationType() { if (whenComplete) { return LocationType.SYNCHRONIZE_COMPLETED; } else { return LocationType.SYNCHRONIZE; } } public String toString() { String text; if (whenComplete) { text= "AFTER SYNCHRONIZE"; } else { text= "AT SYNCHRONIZE"; } if (count != 1) { if (count == 0) { text += " ALL"; } else { text += " " + count; } } return text; } } /** * location identifying a throw trigger point */ private static class ThrowLocation extends Location { /** * count identifying which throw operation should be taken as the trigger point. if not specified * as a parameter this defaults to the first throw. */ private int count; /** * the name of the exception type to which the method belongs or null if any type will do */ private String typeName; /** * construct a location identifying a throw trigger point * @param count count identifying which throw should be taken as the trigger point * @param typeName the name of the exception type associated with the throw operation */ private ThrowLocation(int count, String typeName) { this.count = count; this.typeName = typeName; } /** * create a location identifying a throw trigger point * @param parameters the text of the parameters appended to the location specifier * @return a throw location or null if the parameters does not contain a valid type name */ protected static Location create(String parameters) { String text = parameters.trim(); String typeName = ""; int count; // text may be either blank, a count or ALL if (text.equals("")) { // count defaults to 1 count = 1; } else if (text.equals("ALL")) { // a zero count means all count = 0; } else { try { count = Integer.valueOf(text); } catch (NumberFormatException nfe) { return null; } } // TODO sanity check type name return new ThrowLocation(count, typeName); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { return new ThrowCheckAdapter(cv, transformContext, typeName, count); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { return new ThrowTriggerAdapter(cv, transformContext, typeName, count); } public LocationType getLocationType() { return LocationType.THROW; } public String toString() { String text = "AT THROW"; if (count != 1) { if (count == 0) { text += " ALL"; } else { text += " " + count; } } return text; } } /** * location identifying a method exit trigger point */ private static class ExitLocation extends Location { /** * create a location identifying a method entry trigger point * @param parameters the text of the parameters appended to the location specifier * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters) { if (!parameters.trim().equals("")) { // hmm, not expecting any parameters here return null; } return new ExitLocation(); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { // a line check adapter with line -1 will do the job return new ExitCheckAdapter(cv, transformContext); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { // a line adapter with line -1 will do the job return new ExitTriggerAdapter(cv, transformContext); } public LocationType getLocationType() { return LocationType.EXIT; } public String toString() { return "AT EXIT"; } } /** * location identifying a method exceptional exit trigger point */ private static class ExceptionExitLocation extends Location { /** * create a location identifying a method exceptional exit trigger point * @param parameters the text of the parameters appended to the location specifier * @return a method entry location or null if the parameters is not a blank String */ protected static Location create(String parameters) { if (!parameters.trim().equals("")) { // hmm, not expecting any parameters here return null; } return new ExceptionExitLocation(); } /** * return an adapter which can be used to check whether a method contains a trigger point whose position * matches this location * @return the required adapter */ public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) { // a line check adapter with line -1 will do the job return new ExceptionExitCheckAdapter(cv, transformContext); } /** * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose * position matches this location * @return the required adapter */ public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) { // a line adapter with line -1 will do the job return new ExceptionExitTriggerAdapter(cv, transformContext); } public LocationType getLocationType() { return LocationType.EXCEPTION_EXIT; } public String toString() { return "AT EXCEPTION EXIT"; } } }