Skip to content
Commits on Source (6)
......@@ -34,7 +34,7 @@
<parent>
<groupId>org.jboss.byteman</groupId>
<artifactId>byteman-root</artifactId>
<version>4.0.5</version>
<version>4.0.7</version>
</parent>
<properties>
......@@ -59,6 +59,7 @@
<Agent-Class>org.jboss.byteman.agent.Main</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
......@@ -321,6 +322,22 @@
<argLine>-javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/location/TestAll.btm</argLine>
</configuration>
</execution>
<execution>
<id>location.TestAtNew</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<includes>
<include>org/jboss/byteman/tests/location/TestAtNew.class</include>
</includes>
<argLine>-javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/location/TestAtNew.btm</argLine>
</configuration>
</execution>
<execution>
<id>location.TestCall</id>
<phase>integration-test</phase>
......@@ -1163,6 +1180,22 @@
<argLine>-javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/bugfixes/TestConstructorArgUpcast.btm</argLine>
</configuration>
</execution>
<execution>
<id>bugfixes.TestBindNull</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<includes>
<include>org/jboss/byteman/tests/bugfixes/TestBindNull.class</include>
</includes>
<argLine>-javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/bugfixes/TestBindNull.btm</argLine>
</configuration>
</execution>
<execution>
<id>javaops.TestInnerClasses</id>
<phase>integration-test</phase>
......@@ -1362,6 +1395,22 @@
<argLine>-Dorg.jboss.byteman.compile.to.bytecode -javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/location/TestAll.btm</argLine>
</configuration>
</execution>
<execution>
<id>location.TestAtNew.compiled</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<includes>
<include>org/jboss/byteman/tests/location/TestAtNew.class</include>
</includes>
<argLine>-Dorg.jboss.byteman.compile.to.bytecode -javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/location/TestAtNew.btm</argLine>
</configuration>
</execution>
<execution>
<id>location.TestCall.compiled</id>
<phase>integration-test</phase>
......@@ -2197,6 +2246,22 @@
<argLine>-Dorg.jboss.byteman.compile.to.bytecode -javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/bugfixes/TestInnerClasses.btm</argLine>
</configuration>
</execution>
<execution>
<id>bugfixes.TestBindNull.compiled</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<includes>
<include>org/jboss/byteman/tests/bugfixes/TestBindNull.class</include>
</includes>
<argLine>-Dorg.jboss.byteman.compile.to.bytecode -javaagent:${project.build.directory}/byteman-agent-${project.version}.jar=script:${project.build.testOutputDirectory}/scripts/bugfixes/TestBindNull.btm</argLine>
</configuration>
</execution>
<!-- dynamic rule submission compiled
submit test does not use a script on the command line
instead it sets listener true and uplaods the rules from the test program
......
......@@ -68,6 +68,10 @@ public abstract class Location
return ExitLocation.create(parameters);
case EXCEPTION_EXIT:
return ExceptionExitLocation.create(parameters);
case NEW:
return NewLocation.create(parameters, false);
case NEW_COMPLETED:
return NewLocation.create(parameters, true);
}
return null;
......@@ -998,4 +1002,158 @@ public abstract class Location
}
}
private static class NewLocation extends Location
{
/**
* the name of the new type being created or the empty String if
* no typename was specified
*/
private String typeName;
/**
* count identifying which new operation should be taken as the trigger point.
* if not specified as a parameter this defaults to the first invocation. if
* 'ALL' was specified this takes value 0.
*/
private int count;
/**
* number of array dimensions that should be matched at an array allocation site
* or 0 if plain, non-array object allocations should be matched
*/
int dims;
/**
* 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;
private NewLocation(String typeName, int count, int dims, boolean whenComplete)
{
this.typeName = typeName;
this.count = count;
this.dims = dims;
this.whenComplete = whenComplete;
}
/**
* 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, boolean whenComplete) {
// syntax is AT NEW [{typename}] [{count} | 'ALL']
String text = parameters.trim();
String typeNamePattern = "[A-Za-z][A-Za-z0-9_]+";
String arrayDimsPattern = "(\\[\\])+";
String countPattern = "[0-9]+";
String allPattern = "ALL";
String countText = null;
String typeNameText = null;
String typeName;
int count;
int dims;
if (text.contains(" ")) {
int tailIdx = text.lastIndexOf(" ");
countText = text.substring(tailIdx + 1).trim();
typeNameText = text.substring(0, tailIdx).trim();
} else if (text.matches(allPattern) || text.matches(countPattern)) {
typeNameText = null;
countText = text;
} else {
typeNameText = text;
countText = null;
}
if (countText == null) {
count = 1;
} else if (countText.matches(allPattern)) {
count = 0;
} else if (countText.matches(countPattern)){
try {
count = Integer.valueOf(countText);
} catch (NumberFormatException nfe) {
return null;
}
} else if (countText.matches(arrayDimsPattern)) {
// space was separating array dims from type
// glue it back together and continue.
typeNameText = typeNameText + countText;
count = 1;
} else {
return null;
}
if (typeNameText == null || typeNameText.length() == 0) {
typeName = "";
dims = 0;
} else if (typeNameText.matches(typeNamePattern)) {
typeName = typeNameText;
dims = 0;
} else if (typeNameText.matches(arrayDimsPattern)) {
typeName = "";
dims = typeNameText.length() / 2;
} else {
if (!typeNameText.contains("[")) {
return null;
}
int tailIdx = typeNameText.indexOf("[");
String dimsText = typeNameText.substring(tailIdx);
typeNameText = typeNameText.substring(0, tailIdx);
if (typeNameText.matches(typeNamePattern) && dimsText.matches(arrayDimsPattern)) {
typeName = typeNameText;
dims = dimsText.length() / 2;
} else {
return null;
}
}
return new NewLocation(typeName, count, dims, whenComplete);
}
public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext)
{
if (dims == 0) {
return new NewCheckAdapter(cv, transformContext, typeName, count, whenComplete);
} else {
return new NewArrayCheckAdapter(cv, transformContext, typeName, count, dims, whenComplete);
}
}
public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext)
{
if (dims == 0) {
return new NewTriggerAdapter(cv, transformContext, typeName, count, whenComplete);
} else {
return new NewArrayTriggerAdapter(cv, transformContext, typeName, count, dims, whenComplete);
}
}
public LocationType getLocationType()
{
return (whenComplete ? LocationType.NEW_COMPLETED : LocationType.NEW);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append((whenComplete ? "AFTER NEW" : "AT NEW"));
if (typeName != null) {
builder.append(" ");
builder.append(typeName);
}
for (int i = 0; i < dims; i++) {
builder.append("[]");
}
if (count == 0) {
builder.append(" ALL");
} else if (count != 1) {
builder.append(" ");
builder.append(count);
}
return builder.toString();
}
}
}
......@@ -113,7 +113,7 @@ public enum LocationType
/**
* specifies a location for trigger insertion at return from the trigger method n.b. a trigger will be
* injected at ALL return points
* script syntax : 'AT' 'RETURN'
* script syntax : 'AT' 'EXIT'
*/
EXIT,
......@@ -121,7 +121,21 @@ public enum LocationType
* specifies a location for trigger insertion on exception exit from the trigger method
* script syntax : 'AT' 'EXCEPTION' 'EXIT'
*/
EXCEPTION_EXIT;
EXCEPTION_EXIT,
/**
* specifies a location for trigger insertion at object allocation
* script syntax : 'AT' 'NEW' [{typename}] [ '[]'+ ] [ {count} | 'ALL' ]
*/
NEW,
/**
* specifies a location for trigger insertion after object allocation and initialization
* script syntax : 'AFTER' 'NEW' [{typename}] [ '[]'+ ] [ {count} | 'ALL' ]
*/
NEW_COMPLETED;
public String specifierText()
{
......@@ -187,11 +201,13 @@ public enum LocationType
"AFTER[ \t]*SYNCHRONIZE",
"AT[ \t]*THROW",
"AT[ \t]*EXIT",
"AT[ \t]*EXCEPTION[ \t]*EXIT",
"AT[ \t]*NEW",
"AFTER[ \t]*NEW",
"LINE", // for compatibility
"AT[ \t]*CALL", // for ambiguity :-)
"AFTER[ \t]*CALL", // for ambiguity :-)
"AT[ \t]*RETURN", // for ambiguity :-)
"AT[ \t]*EXCEPTION[ \t]*EXIT" // for ambiguity :-)
"AT[ \t]*RETURN" // for ambiguity :-)
};
private static Pattern[] specifierPatterns = createPatterns();
......@@ -209,10 +225,12 @@ public enum LocationType
SYNCHRONIZE_COMPLETED,
THROW,
EXIT,
EXCEPTION_EXIT,
NEW,
NEW_COMPLETED,
LINE,
INVOKE,
INVOKE_COMPLETED,
EXIT,
EXCEPTION_EXIT
EXIT
};
}
......@@ -35,12 +35,15 @@ import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
/**
* byte code transformer used to introduce byteman events into JBoss code
......@@ -97,6 +100,9 @@ public class Transformer implements ClassFileTransformer {
}
accessEnabler = AccessManager.init(inst);
// initialise the agent version property
setAgentVersion();
}
/**
......@@ -436,6 +442,11 @@ public class Transformer implements ClassFileTransformer {
public static final String DEBUG = BYTEMAN_PACKAGE_PREFIX + "debug";
/**
* system property set to record the currently installed Byteman agent's version
*/
public static final String AGENT_VERSION = BYTEMAN_PACKAGE_PREFIX + "agent.version";
/**
* retained for compatibility
*/
......@@ -1433,6 +1444,38 @@ public class Transformer implements ClassFileTransformer {
return file.mkdirs();
}
}
private void setAgentVersion() throws Exception
{
String version = System.getProperty(AGENT_VERSION);
if (version != null && !version.equals("")) {
throw new Exception("Transformer.setAgentVersion: Byteman agent version already set!");
}
String className = this.getClass().getSimpleName() + ".class";
String classPath = this.getClass().getResource(className).toString();
if (classPath.startsWith("file")) {
// this happens during building -- just ignore
return;
} else if (!classPath.startsWith("jar")) {
throw new Exception("Transformer.setAgentVersion: could not find manifest for agent jar");
}
try {
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
"/META-INF/MANIFEST.MF";
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
version = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
} catch (IOException E) {
// drop through
}
if (version == null || version.equals("")) {
throw new Exception("Transformer.setAgentVersion: could not find manifest implementation version for byteman agent jar");
}
System.setProperty(AGENT_VERSION, version);
}
/**
* Thread local holding a per thread Boolean which is true if triggering is disabled and false if triggering is
* enabled
......
/*
* JBoss, Home of Professional Open Source
* Copyright 2019, 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.adapter;
import org.jboss.byteman.agent.TransformContext;
import org.jboss.byteman.rule.type.TypeHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* asm Adapter class used to check that the target method for a rule exists in a class
*/
public class NewArrayCheckAdapter extends RuleCheckAdapter
{
public NewArrayCheckAdapter(ClassVisitor cv, TransformContext transformContext,
String typeName, int count, int dims, boolean whenComplete)
{
super(cv, transformContext);
this.typeName = typeName;
this.count = count;
this.dims = dims;
this.whenComplete = whenComplete;
}
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions)
{
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (matchTargetMethod(access, name, desc)) {
setVisited();
return new NewArrayCheckMethodAdapter(mv, getTransformContext(), access, name, desc, signature, exceptions);
}
return mv;
}
/**
* a method visitor used to add a rule event trigger call to a method
*/
private class NewArrayCheckMethodAdapter extends RuleCheckMethodAdapter
{
private int access;
private String name;
private String descriptor;
private String signature;
private String[] exceptions;
private boolean visited;
private int visitedCount;
String matchedBaseName;
NewArrayCheckMethodAdapter(MethodVisitor mv, TransformContext transformContext, int access, String name, String descriptor, String signature, String[] exceptions)
{
super(mv, transformContext, access, name, descriptor);
this.access = access;
this.name = name;
this.descriptor = descriptor;
this.signature = signature;
this.exceptions = exceptions;
visitedCount = 0;
matchedBaseName = null;
}
@Override
public void visitTypeInsn(int opcode, String type)
{
boolean triggerReady = false;
int elementDims = TypeHelper.dimCount(type);
if (opcode == Opcodes.ANEWARRAY &&
(count == 0 || visitedCount < count) &&
(dims == elementDims + 1) && matchType(type)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
if (whenComplete) {
// set ready for triggering after the array is created
triggerReady = true;
} else {
// inject a trigger here just before the NEW
setTriggerPoint();
}
}
}
super.visitTypeInsn(opcode, type);
if (triggerReady) {
setTriggerPoint();
}
}
@Override
public void visitIntInsn(int opcode, int operand)
{
boolean triggerReady = false;
if (opcode == Opcodes.NEWARRAY && dims == 1 &&
(count == 0 || visitedCount < count) && matchType(operand)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
if (whenComplete) {
// set ready for triggering after the array is created
triggerReady = true;
} else {
// inject a trigger here just before the NEW
setTriggerPoint();
}
}
}
super.visitIntInsn(opcode, operand);
if (triggerReady) {
setTriggerPoint();
}
}
@Override
public void visitMultiANewArrayInsn(String descriptor, int numDimensions)
{
boolean triggerReady = false;
int totalDims = TypeHelper.dimCount(descriptor);
if (dims == totalDims && (count == 0 || visitedCount < count) && matchType(descriptor)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
if (whenComplete) {
// set ready for triggering after super call
triggerReady = true;
} else {
// inject a trigger here just before the NEW
setTriggerPoint();
}
}
}
super.visitMultiANewArrayInsn(descriptor, numDimensions);
if (triggerReady) {
setTriggerPoint();
}
}
private boolean matchType(int operand)
{
String baseName = null;
// operand identifies a primitive type
switch (operand) {
case Opcodes.T_BOOLEAN:
baseName = "boolean";
break;
case Opcodes.T_BYTE:
baseName = "byte";
break;
case Opcodes.T_CHAR:
baseName = "char";
break;
case Opcodes.T_SHORT:
baseName = "short";
break;
case Opcodes.T_INT:
baseName = "int";
break;
case Opcodes.T_LONG:
baseName = "long";
break;
case Opcodes.T_FLOAT:
baseName = "float";
break;
case Opcodes.T_DOUBLE:
baseName = "double";
break;
default:
// should never happen
break;
}
if(typeName.length() == 0 || typeName.equals(baseName)) {
// save any matched base name so we can use it to type $!
matchedBaseName = baseName;
return true;
}
return false;
}
private boolean matchType(String type)
{
String baseName = TypeHelper.internalizeClass(type, true);
boolean matched = false;
// matches are for the explicitly named type
// or any type if the chosen name is the empty string
if (typeName.length() == 0) {
matched = true;
} else if (typeName.equals(baseName)) {
matched = true;
} else if (!typeName.contains(".") && baseName.contains(".")) {
int tailIdx = baseName.lastIndexOf(".");
matched = typeName.equals(baseName.substring(tailIdx+1));
}
if (matched) {
// save any matched base name so we can use it to type $!
matchedBaseName = baseName;
}
return matched;
}
@Override
public String getNewTypeParamDescriptor()
{
StringBuilder builder = new StringBuilder(matchedBaseName);
for (int i = 0; i < dims ; i++) {
builder.append("[]");
}
return TypeHelper.externalizeType(builder.toString());
}
}
private String typeName;
private int count;
private int dims;
private boolean whenComplete;
}
\ No newline at end of file
/*
* JBoss, Home of Professional Open Source
* Copyright 2019, 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.adapter;
import org.jboss.byteman.agent.TransformContext;
import org.jboss.byteman.rule.type.TypeHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* asm Adapter class used to add a rule event trigger call to a method of som egiven class
*/
public class NewArrayTriggerAdapter extends RuleTriggerAdapter
{
public NewArrayTriggerAdapter(ClassVisitor cv, TransformContext transformContext,
String typeName, int count, int dims, boolean whenComplete)
{
super(cv, transformContext);
this.typeName = typeName;
this.count = count;
this.dims = dims;
this.whenComplete = whenComplete;
}
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions)
{
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (injectIntoMethod(name, desc)) {
if (name.equals("<init>")) {
return new NewArrayTriggerConstructorAdapter(mv, getTransformContext(), access, name, desc, signature, exceptions);
} else {
return new NewArrayTriggerMethodAdapter(mv, getTransformContext(), access, name, desc, signature, exceptions);
}
}
return mv;
}
/**
* a method visitor used to add a rule event trigger call to a method
*/
private class NewArrayTriggerMethodAdapter extends RuleTriggerMethodAdapter
{
/**
* flag used by subclass to avoid inserting trigger until after super constructor has been called
*/
protected boolean latched;
private int visitedCount;
private String matchedBaseName;
NewArrayTriggerMethodAdapter(MethodVisitor mv, TransformContext transformContext, int access, String name, String descriptor, String signature, String[] exceptions)
{
super(mv, transformContext, access, name, descriptor, signature, exceptions);
visitedCount = 0;
latched = false;
matchedBaseName = null;
}
@Override
public String getNewClassName()
{
StringBuilder builder = new StringBuilder(matchedBaseName);
for (int i = 0; i < dims ; i++) {
builder.append("[]");
}
return builder.toString();
}
@Override
public void visitTypeInsn(int opcode, String type)
{
boolean triggerReady = false;
// n.b. have to skip any ANEWARRAY injected into the byteman trigger code
int elementDims = TypeHelper.dimCount(type);
if (!inBytemanTrigger() &&
opcode == Opcodes.ANEWARRAY &&
(count == 0 || visitedCount < count) &&
(dims == elementDims + 1) && matchType(type)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
// record the name of the class being created
if (whenComplete) {
// set ready for triggering after the array is created
triggerReady = true;
} else {
// inject a trigger here just before the NEW
injectTriggerPoint();
}
}
}
super.visitTypeInsn(opcode, type);
if (triggerReady) {
injectTriggerPoint();
}
}
@Override
public void visitIntInsn(int opcode, int operand)
{
boolean triggerReady = false;
if (opcode == Opcodes.NEWARRAY && dims == 1 &&
(count == 0 || visitedCount < count) && matchType(operand)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
if (whenComplete) {
// set ready for triggering after the array is created
triggerReady = true;
} else {
// inject a trigger here just before the NEW
injectTriggerPoint();
}
}
}
super.visitIntInsn(opcode, operand);
if (triggerReady) {
injectTriggerPoint();
}
}
@Override
public void visitMultiANewArrayInsn(String descriptor, int numDimensions)
{
boolean triggerReady = false;
int totalDims = TypeHelper.dimCount(descriptor);
if (dims == totalDims && (count == 0 || visitedCount < count) && matchType(descriptor)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
if (whenComplete) {
// set ready for triggering after super call
triggerReady = true;
} else {
// inject a trigger here just before the NEW
injectTriggerPoint();
}
}
}
super.visitMultiANewArrayInsn(descriptor, numDimensions);
if (triggerReady) {
injectTriggerPoint();
}
}
private boolean matchType(int operand)
{
String baseName = null;
// operand identifies a primitive type
switch (operand) {
case Opcodes.T_BOOLEAN:
baseName = "boolean";
break;
case Opcodes.T_BYTE:
baseName = "byte";
break;
case Opcodes.T_CHAR:
baseName = "char";
break;
case Opcodes.T_SHORT:
baseName = "short";
break;
case Opcodes.T_INT:
baseName = "int";
break;
case Opcodes.T_LONG:
baseName = "long";
break;
case Opcodes.T_FLOAT:
baseName = "float";
break;
case Opcodes.T_DOUBLE:
baseName = "double";
break;
default:
// should never happen
break;
}
if(typeName.length() == 0 || typeName.equals(baseName)) {
// save any matched base name so we can use it to type $!
matchedBaseName = baseName;
return true;
}
return false;
}
private boolean matchType(String type)
{
String baseName = TypeHelper.internalizeClass(type, true);
boolean matched = false;
// matches are for the explicitly named type
// or any type if the chosen name is the empty string
if (typeName.length() == 0) {
matched = true;
} else if (typeName.equals(baseName)) {
matched = true;
} else if (!typeName.contains(".") && baseName.contains(".")) {
int tailIdx = baseName.lastIndexOf(".");
matched = typeName.equals(baseName.substring(tailIdx+1));
}
if (matched) {
// save any matched base name so we can use it to type $!
matchedBaseName = baseName;
}
return matched;
}
@Override
public Type getReturnBindingType()
{
// n.b. we use externalizeType here as we need left square brackets followed by a bracketing L and colon
return Type.getObjectType(TypeHelper.externalizeType(getNewClassName()));
}
}
/**
* a method visitor used to add a rule event trigger call to a constructor -- this has to make sure
* the super constructor has been called before allowing a trigger call to be compiled
*/
private class NewArrayTriggerConstructorAdapter extends NewArrayTriggerMethodAdapter
{
NewArrayTriggerConstructorAdapter(MethodVisitor mv, TransformContext transformContext, int access, String name, String descriptor, String signature, String[] exceptions)
{
super(mv, transformContext, access, name, descriptor, signature, exceptions);
// ensure we don't transform calls before the super constructor is called
latched = true;
}
public void visitMethodInsn(
final int opcode,
final String owner,
final String name,
final String desc,
boolean itf)
{
super.visitMethodInsn(opcode, owner, name, desc, itf);
if (latched && isSuperOrSiblingConstructorCall(opcode, owner, name)) {
latched = false;
}
}
}
protected String typeName;
protected int count;
protected int dims;
protected boolean whenComplete;
}
\ No newline at end of file
/*
* JBoss, Home of Professional Open Source
* Copyright 2019, 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.adapter;
import org.jboss.byteman.agent.TransformContext;
import org.jboss.byteman.rule.type.Type;
import org.jboss.byteman.rule.type.TypeHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* asm Adapter class used to check that the target method for a rule exists in a class
*/
public class NewCheckAdapter extends RuleCheckAdapter
{
public NewCheckAdapter(ClassVisitor cv, TransformContext transformContext,
String typeName, int count, boolean whenComplete)
{
super(cv, transformContext);
this.typeName = typeName;
this.count = count;
this.whenComplete = whenComplete;
}
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions)
{
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (matchTargetMethod(access, name, desc)) {
setVisited();
return new NewCheckMethodAdapter(mv, getTransformContext(), access, name, desc, signature, exceptions);
}
return mv;
}
/**
* a method visitor used to add a rule event trigger call to a method
*/
private class NewCheckMethodAdapter extends RuleCheckMethodAdapter
{
private int access;
private String name;
private String descriptor;
private String signature;
private String[] exceptions;
private boolean visited;
private int visitedCount;
private boolean triggerReady;
String matchedBaseName;
NewCheckMethodAdapter(MethodVisitor mv, TransformContext transformContext, int access, String name, String descriptor, String signature, String[] exceptions)
{
super(mv, transformContext, access, name, descriptor);
this.access = access;
this.name = name;
this.descriptor = descriptor;
this.signature = signature;
this.exceptions = exceptions;
triggerReady = false;
visitedCount = 0;
matchedBaseName = null;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
{
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
if (triggerReady && opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
// inject a trigger here after the NEW and <init> call
setTriggerPoint();
triggerReady = false;
}
}
@Override
public void visitTypeInsn(int opcode, String type)
{
if (opcode == Opcodes.NEW && (count == 0 || visitedCount < count) && matchType(type)) {
// a relevant invocation occurs in the called method
visitedCount++;
if (count == 0 || visitedCount == count) {
if (whenComplete) {
// set ready for triggering at the next invokespecial
triggerReady = true;
} else {
// inject a trigger here just before the NEW
setTriggerPoint();
}
}
}
super.visitTypeInsn(opcode, type);
}
private boolean matchType(String type)
{
String baseName = TypeHelper.internalizeClass(type, true);
boolean matched = false;
// matches are for the explicitly named type
// or any type if the chosen name is the empty string
if (typeName.length() == 0) {
matched = true;
} else if (typeName.equals(baseName)) {
matched = true;
} else if (!typeName.contains(".") && baseName.contains(".")) {
int tailIdx = baseName.lastIndexOf(".");
matched = typeName.equals(baseName.substring(tailIdx+1));
}
if (matched) {
// save any matched base name so we can use it to type $!
matchedBaseName = baseName;
}
return matched;
}
@Override
public String getNewTypeParamDescriptor()
{
return TypeHelper.externalizeType(matchedBaseName);
}
}
private String typeName;
private int count;
private boolean whenComplete;
}
\ No newline at end of file
/*
* JBoss, Home of Professional Open Source
* Copyright 2019, 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.adapter;
import org.jboss.byteman.agent.TransformContext;
import org.jboss.byteman.rule.type.TypeHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* asm Adapter class used to add a rule event trigger call to a method of som egiven class
*/
public class NewTriggerAdapter extends RuleTriggerAdapter
{
public NewTriggerAdapter(ClassVisitor cv, TransformContext transformContext,
String typeName, int count, boolean whenComplete)
{
super(cv, transformContext);
this.typeName = typeName;
this.count = count;
this.whenComplete = whenComplete;
}
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions)
{
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (injectIntoMethod(name, desc)) {
if (name.equals("<init>")) {
return new NewTriggerConstructorAdapter(mv, getTransformContext(), access, name, desc, signature, exceptions);
} else {
return new NewTriggerMethodAdapter(mv, getTransformContext(), access, name, desc, signature, exceptions);
}
}
return mv;
}
/**
* a method visitor used to add a rule event trigger call to a method
*/
private class NewTriggerMethodAdapter extends RuleTriggerMethodAdapter
{
/**
* flag used by subclass to avoid inserting trigger until after super constructor has been called
*/
protected boolean latched;
private int visitedCount;
private boolean triggerReady;
private String matchedBaseName;
NewTriggerMethodAdapter(MethodVisitor mv, TransformContext transformContext, int access, String name, String descriptor, String signature, String[] exceptions)
{
super(mv, transformContext, access, name, descriptor, signature, exceptions);
visitedCount = 0;
latched = false;
triggerReady = false;
matchedBaseName = null;
}
@Override
public String getNewClassName()
{
return matchedBaseName;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
{
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
if (triggerReady && opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
if (!latched) {
// inject a trigger here after the NEW and <init> call
injectTriggerPoint();
} else {
transformContext.warn(name, descriptor, "cannot inject AFTER NEW rule into constructor before super constructor has been called");
}
triggerReady = false;
}
}
@Override
public void visitTypeInsn(int opcode, String type)
{
if (opcode == Opcodes.NEW && (visitedCount == 0 || visitedCount < count) && matchType(type)) {
// a relevant invocation occurs in the called method
visitedCount++;
if(visitedCount == 0 || visitedCount == count) {
if(whenComplete) {
// set ready for triggering at the next invokespecial
triggerReady = true;
} else {
// cannot inject unless the object has been initialized
if(!latched) {
// inject a trigger here just before the NEW
injectTriggerPoint();
} else {
transformContext.warn(name, descriptor, "cannot inject AT NEW rule into constructor before super constructor has been called");
}
}
}
}
super.visitTypeInsn(opcode, type);
}
private boolean matchType(String type)
{
String baseName = TypeHelper.internalizeClass(type, true);
boolean matched = false;
// matches are for the explicitly named type
// or any type if the chosen name is the empty string
if (typeName.length() == 0) {
matched = true;
} else if (typeName.equals(baseName)) {
matched = true;
} else if (!typeName.contains(".") && baseName.contains(".")) {
int tailIdx = baseName.lastIndexOf(".");
matched = typeName.equals(baseName.substring(tailIdx+1));
}
if (matched) {
// save any matched base name so we can use it to type $!
matchedBaseName = baseName;
}
return matched;
}
@Override
public Type getReturnBindingType()
{
// n.b. we use externalizeClass here as we don't need a bracketing L and colon
return Type.getObjectType(TypeHelper.externalizeClass(getNewClassName()));
}
}
/**
* a method visitor used to add a rule event trigger call to a constructor -- this has to make sure
* the super constructor has been called before allowing a trigger call to be compiled
*/
private class NewTriggerConstructorAdapter extends NewTriggerMethodAdapter
{
NewTriggerConstructorAdapter(MethodVisitor mv, TransformContext transformContext, int access, String name, String descriptor, String signature, String[] exceptions)
{
super(mv, transformContext, access, name, descriptor, signature, exceptions);
// ensure we don't transform calls before the super constructor is called
latched = true;
}
public void visitMethodInsn(
final int opcode,
final String owner,
final String name,
final String desc,
boolean itf)
{
super.visitMethodInsn(opcode, owner, name, desc, itf);
if (latched && isSuperOrSiblingConstructorCall(opcode, owner, name)) {
latched = false;
}
}
}
protected String typeName;
protected int count;
protected boolean whenComplete;
}
\ No newline at end of file
......@@ -23,6 +23,7 @@
*/
package org.jboss.byteman.agent.adapter;
import org.jboss.byteman.agent.Location;
import org.jboss.byteman.rule.helper.Helper;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Label;
......@@ -100,6 +101,7 @@ public class RuleCheckMethodAdapter extends RuleMethodAdapter {
} else if (binding.isReturn()) {
// this is a valid reference in an AT EXIT rule and in an AFTER INVOKE
// but only if the corresponding returning or called method is non-void
// it is also valid in an AFTER NEW rule as a reference to the new object/array
LocationType locationType = rule.getTargetLocation().getLocationType();
if (locationType == LocationType.EXIT) {
if ("void".equals(getReturnBindingType())) {
......@@ -113,8 +115,10 @@ public class RuleCheckMethodAdapter extends RuleMethodAdapter {
transformContext.warn(name, descriptor, "found return value binding " + binding + " checking void called method in AFTER INVOKE rule");
}
} else if (locationType == LocationType.NEW_COMPLETED) {
binding.setDescriptor(getNewTypeParamDescriptor());
} else {
Helper.verbose("RuleCheckMethodAdapter.checkBindings : found return value binding " + binding + " in rule which is neither AT EXIT nor AFTER INVOKE " + rule.getName());
Helper.verbose("RuleCheckMethodAdapter.checkBindings : found return value binding " + binding + " in rule which is neither AT EXIT nor AFTER INVOKE nor AFTER NEW " + rule.getName());
transformContext.warn(name, descriptor, "found return value binding " + binding + " in rule which is neither AT EXIT nor AFTER INVOKE");
}
......@@ -140,6 +144,13 @@ public class RuleCheckMethodAdapter extends RuleMethodAdapter {
}
} else if (binding.isTriggerClass() || binding.isTriggerMethod()) {
// this is ok
} else if (binding.isNewClass()) {
LocationType locationType = rule.getTargetLocation().getLocationType();
if (locationType != LocationType.NEW && locationType != LocationType.NEW_COMPLETED) {
Helper.verbose("RuleCheckMethodAdapter.checkBindings : found new class binding $NEWCLASS in non-AT NEW rule " + rule.getName());
transformContext.warn(name, descriptor, "found new class binding $NEWCLASS in non-AT NEW rule");
}
} else if (binding.isLocalVar()){
// make sure we have a local variable with the correct name
String localVarName = binding.getName().substring(1);
......@@ -207,6 +218,17 @@ public class RuleCheckMethodAdapter extends RuleMethodAdapter {
super.visitEnd();
}
/**
* method overridden by AT NEW method check adapter allowing String value for the type name provided
* in the NEW location spec to be retrieved.
* this default version should never get invoked
* @return String value for
*/
protected String getNewTypeParamDescriptor()
{
throw new RuntimeException("RuleTriggerMethodAdapter.getNewTypeParamDescriptor() : should never get called!");
}
private List<Label> triggerPoints;
private String returnBindingType;
}
\ No newline at end of file
......@@ -69,6 +69,16 @@ public class RuleTriggerMethodAdapter extends RuleGeneratorAdapter
throw new RuntimeException("RuleTriggerMethodAdapter.getInvokedTypes() : should never get called!");
}
/**
* method overridden by AT NEW method trigger adapter allowing String value for NEWCLASS binding to
* be retrieved.,
* this default version should never get invoked
* @return String value for NEWCLASS binding
*/
public String getNewClassName() {
throw new RuntimeException("RuleTriggerMethodAdapter.getNewClassName() : should never get called!");
}
/**
* method overridden by AT INVOKE method adapter allowing the type of the $! binding to be identified.
* this default version should only get invoked for an AT EXIT rule where it returns the trigger method
......@@ -157,7 +167,7 @@ public class RuleTriggerMethodAdapter extends RuleGeneratorAdapter
// have to add a local var to store the value so track that requirement
callArrayBindings.add(binding);
bindInvokeParams = true;
} else if (binding.isTriggerClass() || binding.isTriggerMethod()) {
} else if (binding.isTriggerClass() || binding.isTriggerMethod() || binding.isNewClass()) {
callArrayBindings.add(binding);
binding.setDescriptor("java.lang.String");
}
......@@ -245,6 +255,15 @@ public class RuleTriggerMethodAdapter extends RuleGeneratorAdapter
} else {
return -1;
}
} else if (b1.isNewClass()) {
if(b2.isParam() || b2.isLocalVar() || b2.isParamCount() || b2.isParamArray() || b2.isInvokeParamArray() || b2.isTriggerClass() || b2.isTriggerMethod()) {
// param, local and param count, param array, invoke param array and trigger class and trigger method bindings precede new class
return 1;
} else if(b2.isNewClass()) {
return 0;
} else {
return -1;
}
} else {
// return var always sorts last
return 1;
......@@ -482,6 +501,9 @@ public class RuleTriggerMethodAdapter extends RuleGeneratorAdapter
} else if (binding.isTriggerMethod()) {
String triggerMethodName = name + TypeHelper.internalizeDescriptor(descriptor);
visitLdcInsn(triggerMethodName);
} else if (binding.isNewClass()) {
String typeName = getNewClassName();
visitLdcInsn(typeName);
} else if (binding.isThrowable() | binding.isReturn()) {
loadLocal(saveSlot);
box(saveValueType);
......
......@@ -38,6 +38,7 @@ import java.util.List;
import org.jboss.byteman.agent.AccessEnabler;
import org.jboss.byteman.agent.AccessManager;
import org.jboss.byteman.agent.HelperManager;
import org.jboss.byteman.agent.Location;
import org.jboss.byteman.agent.LocationType;
import org.jboss.byteman.agent.RuleScript;
import org.jboss.byteman.agent.ScriptRepository;
......@@ -484,7 +485,14 @@ public class RuleCheck {
binding.setDescriptor(paramTypes.get(idx - 1));
}
} else if (binding.isReturn()) {
if (rule.getTargetLocation().getLocationType() != LocationType.INVOKE_COMPLETED) {
LocationType locationType = rule.getTargetLocation().getLocationType();
if (locationType == LocationType.INVOKE_COMPLETED) {
warning("WARNING : Cannot infer type for $! in AFTER INVOKE rule \"" + rule.getName() + "\"");
binding.setDescriptor("void");
} else if (locationType == LocationType.NEW_COMPLETED) {
warning("WARNING : Cannot infer type for $! in AFTER NEW rule \"" + rule.getName() + "\"");
binding.setDescriptor("void");
} else {
// return type is on end of list
String returnType = paramTypes.get(paramCount);
if("void".equals(returnType)) {
......@@ -493,9 +501,6 @@ public class RuleCheck {
} else {
binding.setDescriptor(returnType);
}
} else {
warning("WARNING : Cannot infer type for $! in AFTER INVOKE rule \"" + rule.getName() + "\"");
binding.setDescriptor("void");
}
} else if (binding.isLocalVar()) {
warning("WARNING : Cannot typecheck local variable " + binding.getName() + " in rule \"" + rule.getName() + "\"");
......
......@@ -716,7 +716,8 @@ public class Rule
Binding binding = iterator.next();
// these bindings are typed via the descriptor installed during trigger injection
// note that the return type has to be done this way because it may represent the
// trigger method return type or the invoke method return type
// trigger method return type or the invoke method return type or a new object or
// array type
if (binding.isParam() || binding.isLocalVar() || binding.isReturn()) {
String typeName = binding.getDescriptor();
String[] typeAndArrayBounds = typeName.split("\\[");
......@@ -737,7 +738,7 @@ public class Rule
binding.setType(Type.I);
} else if (binding.isParamArray() || binding.isInvokeParamArray()) {
binding.setType(Type.OBJECT.arrayType());
} else if (binding.isTriggerClass() || binding.isTriggerMethod()) {
} else if (binding.isTriggerClass() || binding.isTriggerMethod() || binding.isNewClass()) {
binding.setType(Type.STRING);
}
}
......
......@@ -99,6 +99,9 @@ public class Binding extends RuleElement
} else if (name.equals("$METHOD")) {
// $* refers to the parameters for the trigger method supplied as an Object array
index = DollarExpression.TRIGGER_METHOD_IDX;
} else if (name.equals("$NEWCLASS")) {
// $* refers to the parameters for the trigger method supplied as an Object array
index = DollarExpression.NEW_CLASS_IDX;
} else if (name.matches("\\$[A-Za-z].*")) {
// $AAAAA refers to a local variable in the trigger method
index = DollarExpression.LOCAL_IDX;
......@@ -308,7 +311,7 @@ public class Binding extends RuleElement
if (type.isPrimitive()) {
// if the assigment involves a type conversion then we need to rebox the value
result = rebox(value.getType(), type, result);
} else if (doCheckCast) {
} else if (result != null && doCheckCast) {
if (type == Type.STRING) {
// force conversion to String
result = result.toString();
......@@ -490,6 +493,11 @@ public class Binding extends RuleElement
return index == DollarExpression.TRIGGER_METHOD_IDX;
}
public boolean isNewClass()
{
return index == DollarExpression.NEW_CLASS_IDX;
}
public int getIndex()
{
return index;
......
......@@ -78,6 +78,8 @@ public class DollarExpression extends AssignableExpression
name = "$CLASS";
} else if (index == TRIGGER_METHOD_IDX){
name = "$METHOD";
} else if (index == NEW_CLASS_IDX){
name = "$NEWCLASS";
} else {
name = "$" + Integer.toString(index);
}
......@@ -349,4 +351,8 @@ public class DollarExpression extends AssignableExpression
* index of $METHOD variable which is bound to a String identifying the trigger method and signature
*/
public final static int TRIGGER_METHOD_IDX = -10;
/**
* index of $METHOD variable which is bound to a String identifying the trigger method and signature
*/
public final static int NEW_CLASS_IDX = -11;
}
......@@ -222,6 +222,8 @@ public class ExpressionHelper
expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_CLASS_IDX);
} else if (text.equals("$METHOD")) {
expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_METHOD_IDX);
} else if (text.equals("$NEWCLASS")) {
expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.NEW_CLASS_IDX);
} else {
expr = new DollarExpression(rule, type, exprTree, text.substring(1));
}
......@@ -756,6 +758,8 @@ public class ExpressionHelper
expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_CLASS_IDX);
} else if (text.equals("$METHOD")) {
expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_METHOD_IDX);
} else if (text.equals("$NEWCLASS")) {
expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.NEW_CLASS_IDX);
} else {
expr = new DollarExpression(rule, type, exprTree, text.substring(1));
}
......
......@@ -23,6 +23,8 @@
*/
package org.jboss.byteman.rule.type;
import org.jboss.byteman.rule.exception.TypeException;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
......@@ -272,7 +274,15 @@ public class TypeGroup {
} else if (clazz.isPrimitive()) {
return typeTable.get(clazz.getName());
} else {
String name = clazz.getCanonicalName();
String name;
if (clazz.isMemberClass()) {
String simpleName = clazz.getSimpleName();
Class outerClazz = clazz.getDeclaringClass();
Type outerType = ensureType(outerClazz);
name = outerType.getName() + "$" + simpleName;
} else {
name = clazz.getCanonicalName();
}
Type type = typeTable.get(name);
if (type == null) {
type = create(name, clazz);
......
......@@ -124,20 +124,59 @@ public class TypeHelper {
}
/**
* convert a classname from external form to canonical form i.e. replace
* all slashes with dots
* convert a classname from external form to canonical form. equivalent
* to calling internalizeClass(className, false).
*
* @param className the external name
* @return the canonical name
*/
public static String internalizeClass(String className)
{
String result = className;
int length = result.length();
if (result.charAt(length - 1) == ';') {
result = result.substring(1, length - 2);
return internalizeClass(className, false);
}
/**
* convert a classname from external form to canonical form. equivalent
* to calling internalizeClass(className, false).
* i.e. replace all slashes with dots, replace each leading square left
* brace with a corresponding trailing square brace pair and remove any
* L and colon characters bracketing the base type name.
*
* if arrayBaseOnly is true then do not append trailing brace pairs
*
* @param className the external name
* @param arrayBaseOnly omit trailing brace pairs when this is true
* @return the canonical name
*/
public static String internalizeClass(String className, boolean arrayBaseOnly)
{
String result;
int arrayDims = 0;
while(className.charAt(arrayDims) == '[') {
arrayDims++;
}
int length = className.length();
if (className.charAt(length - 1) == ';') {
result = className.substring(arrayDims + 1, length - 1);
} else {
result = className.substring(arrayDims, length);
// special check for primitive types
for (int i = 0; i < externalNames.length; i++) {
if (externalNames[i].equals(result)) {
result = internalNames[i];
break;
}
}
}
result = result.replace('/', '.');
if (!arrayBaseOnly) {
for (int i = 0; i < arrayDims; i++) {
result = result + "[]";
}
}
return result;
}
......@@ -428,4 +467,23 @@ public class TypeHelper {
return "";
}
}
/**
* idenitfy the number of array dimensions encoded in the supplied type
* @param type a type in external format
* @return the number of array dimensions encoded in the supplied type
*/
public static int dimCount(String type)
{
int start = 0;
int dims = 0;
if (type.startsWith("L") && type.endsWith(";")) {
start = 1;
}
while (start < type.length() && type.charAt(start++)== '[') {
dims++;
}
return dims;
}
}
......@@ -28,7 +28,15 @@ package org.jboss.byteman.tests.auxiliary;
*/
public class Parent
{
private static int nextId = 0;
private static synchronized int nextId() { return nextId++; }
private int id;
public Parent()
{
id = nextId();
}
public int getId() { return id; }
}
/*
* JBoss, Home of Professional Open Source
* Copyright 2019, 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.tests.bugfixes;
import org.jboss.byteman.tests.Test;
/**
* regression test for BYTEMAN-376
*/
public class TestBindNull extends Test
{
public TestBindNull()
{
super(TestBindNull.class.getCanonicalName());
}
private static int[] ints = new int[] { 0, 1, 2 };
public static int[] getInts(int i) {
return (i == 0 ? null : ints);
}
public void test()
{
try {
triggerMethod();
} catch (Exception e) {
log(e);
}
checkOutput();
}
public static String makeString(int[] ints)
{
if (ints == null) {
return null;
}
StringBuilder builder = new StringBuilder();
String prefix = "[";
for(int i = 0; i < ints.length; i++) {
builder.append(prefix);
builder.append(ints[i]);
prefix = ",";
}
builder.append("]");
return builder.toString();
}
public void triggerMethod()
{
log("TestBindNull.triggerMethod()");
}
@Override
public String getExpected()
{
logExpected("getInts(0) is " + makeString(getInts(0)));
logExpected("getInts(1) is " + makeString(getInts(1)));
logExpected("TestBindNull.triggerMethod()");
return super.getExpected();
}
}
......@@ -49,6 +49,7 @@ public class TestRuleCheck extends Test
addBtmScript(checker, new File("src/test/resources/scripts/location"));
addBtmScript(checker, new File("src/test/resources/scripts/misc"));
checker.addPackage("org.jboss.byteman.tests.auxiliary");
checker.addPackage("org.jboss.byteman.tests.location");
checker.addPackage("org.jboss.byteman.tests.bugfixes");
checker.addPackage("org.jboss.byteman.tests.javaops");
checker.addPackage("org.jboss.byteman.tests.helpertests");
......