Commit 21b95d74 authored by Andrius Merkys's avatar Andrius Merkys

New upstream version 4.0.7

parent 8e513b5e
......@@ -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.
*