diff options
author | 2017-07-25 14:06:34 -0700 | |
---|---|---|
committer | 2017-07-27 13:48:39 -0700 | |
commit | 47d49b842a87823df6ffda30bb21bd6d8e4d8641 (patch) | |
tree | e11ac9e92ed4539fc11f6e49375b44acce5f74a6 | |
parent | bebd7bd02a073056851cc1b0942c53ce0d2ee206 (diff) |
Tests for JVMTI can_access_local_variables functions
These are tests for functions gated by can_access_local_variables and
other supporting code.
Test: ./test.py --host -j50
Bug: 34414073
Bug: 36892980
Change-Id: Iba7e9207683372fbe73510442856553510d90933
68 files changed, 4983 insertions, 4 deletions
diff --git a/test/1911-get-local-var-table/expected.txt b/test/1911-get-local-var-table/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/1911-get-local-var-table/expected.txt diff --git a/test/1911-get-local-var-table/info.txt b/test/1911-get-local-var-table/info.txt new file mode 100644 index 0000000000..43955e5456 --- /dev/null +++ b/test/1911-get-local-var-table/info.txt @@ -0,0 +1 @@ +Tests getting local variable table from JVMTI diff --git a/test/1911-get-local-var-table/run b/test/1911-get-local-var-table/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1911-get-local-var-table/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1911-get-local-var-table/src/Main.java b/test/1911-get-local-var-table/src/Main.java new file mode 100644 index 0000000000..4e0c9e4506 --- /dev/null +++ b/test/1911-get-local-var-table/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1911.run(); + } +} diff --git a/test/1911-get-local-var-table/src/art/Breakpoint.java b/test/1911-get-local-var-table/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1911-get-local-var-table/src/art/Locals.java b/test/1911-get-local-var-table/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1911-get-local-var-table/src/art/Suspension.java b/test/1911-get-local-var-table/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1911-get-local-var-table/src/art/Test1911.java b/test/1911-get-local-var-table/src/art/Test1911.java new file mode 100644 index 0000000000..4dd9054bf0 --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Test1911.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashSet; +import java.util.Set; + +public class Test1911 { + // Class/dex file containing the following class. + // + // CLASS_BYTES generated with java version 1.8.0_45: javac -g art/Target.java + // DEX_BYTES generated with dx version 1.14: dx --dex --output=./classes.dex art/Target.class + // + // package art; + // import java.util.ArrayList; + // public class Target { + // public int zzz; + // public Target(int xxx) { + // int q = xxx * 4; + // zzz = q; + // } + // public static void doNothing(Object... objs) { doNothing(objs); } + // public void doSomething(int x) { + // doNothing(this); + // int y = x + 3; + // for (int z = 0; z < y * x; z++) { + // float q = y - z; + // double i = 0.3d * q; + // doNothing(q, i); + // } + // Object o = new Object(); + // ArrayList<Integer> i = new ArrayList<>(); + // int p = 4 | x; + // long q = 3 * p; + // doNothing(p, q, o, i); + // } + // } + public static byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQARgoABAAuCQANAC8KAA0AMAcAMQY/0zMzMzMzMwoAMgAzCgA0ADUHADYKAAkALgoA" + + "NwA4CgA5ADoHADsBAAN6enoBAAFJAQAGPGluaXQ+AQAEKEkpVgEABENvZGUBAA9MaW5lTnVtYmVy" + + "VGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAMTGFydC9UYXJnZXQ7AQADeHh4AQAB" + + "cQEACWRvTm90aGluZwEAFihbTGphdmEvbGFuZy9PYmplY3Q7KVYBAARvYmpzAQATW0xqYXZhL2xh" + + "bmcvT2JqZWN0OwEAC2RvU29tZXRoaW5nAQABRgEAAWkBAAFEAQABegEAAXgBAAF5AQABbwEAEkxq" + + "YXZhL2xhbmcvT2JqZWN0OwEAFUxqYXZhL3V0aWwvQXJyYXlMaXN0OwEAAXABAAFKAQAWTG9jYWxW" + + "YXJpYWJsZVR5cGVUYWJsZQEAKkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdl" + + "cjs+OwEADVN0YWNrTWFwVGFibGUBAApTb3VyY2VGaWxlAQALVGFyZ2V0LmphdmEMABAAPAwADgAP" + + "DAAZABoBABBqYXZhL2xhbmcvT2JqZWN0BwA9DAA+AD8HAEAMAD4AQQEAE2phdmEvdXRpbC9BcnJh" + + "eUxpc3QHAEIMAD4AQwcARAwAPgBFAQAKYXJ0L1RhcmdldAEAAygpVgEAD2phdmEvbGFuZy9GbG9h" + + "dAEAB3ZhbHVlT2YBABQoRilMamF2YS9sYW5nL0Zsb2F0OwEAEGphdmEvbGFuZy9Eb3VibGUBABUo" + + "RClMamF2YS9sYW5nL0RvdWJsZTsBABFqYXZhL2xhbmcvSW50ZWdlcgEAFihJKUxqYXZhL2xhbmcv" + + "SW50ZWdlcjsBAA5qYXZhL2xhbmcvTG9uZwEAEyhKKUxqYXZhL2xhbmcvTG9uZzsAIQANAAQAAAAB" + + "AAEADgAPAAAAAwABABAAEQABABIAAABYAAIAAwAAAA4qtwABGwdoPSoctQACsQAAAAIAEwAAABIA" + + "BAAAAAUABAAGAAgABwANAAgAFAAAACAAAwAAAA4AFQAWAAAAAAAOABcADwABAAgABgAYAA8AAgCJ" + + "ABkAGgABABIAAAAvAAEAAQAAAAUquAADsQAAAAIAEwAAAAYAAQAAAAkAFAAAAAwAAQAAAAUAGwAc" + + "AAAAAQAdABEAAQASAAABWAAFAAgAAACCBL0ABFkDKlO4AAMbBmA9Az4dHBtoogAvHB1khjgEFAAF" + + "FwSNazkFBb0ABFkDFwS4AAdTWQQYBbgACFO4AAOEAwGn/9C7AARZtwABTrsACVm3AAo6BAcbgDYF" + + "BhUFaIU3Bge9AARZAxUFuAALU1kEFga4AAxTWQUtU1kGGQRTuAADsQAAAAQAEwAAADYADQAAAAsA" + + "CwAMAA8ADQAYAA4AHgAPACcAEAA+AA0ARAASAEwAEwBVABQAWgAVAGEAFgCBABcAFAAAAGYACgAe" + + "ACAAGAAeAAQAJwAXAB8AIAAFABEAMwAhAA8AAwAAAIIAFQAWAAAAAACCACIADwABAA8AcwAjAA8A" + + "AgBMADYAJAAlAAMAVQAtAB8AJgAEAFoAKAAnAA8ABQBhACEAGAAoAAYAKQAAAAwAAQBVAC0AHwAq" + + "AAQAKwAAAAoAAv0AEQEB+gAyAAEALAAAAAIALQ=="); + public static byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCQtgjEV631Ma/btYyIy2IzqHWNN+nZiwl0BQAAcAAAAHhWNBIAAAAAAAAAANQEAAAk" + + "AAAAcAAAAA0AAAAAAQAABwAAADQBAAABAAAAiAEAAAkAAACQAQAAAQAAANgBAAB8AwAA+AEAAB4D" + + "AAAmAwAAKQMAACwDAAAvAwAAMgMAADYDAAA6AwAAPgMAAEIDAABQAwAAZAMAAHcDAACMAwAAngMA" + + "ALIDAADJAwAA9QMAAAIEAAAFBAAACQQAAA0EAAAiBAAALQQAADoEAAA9BAAAQAQAAEYEAABJBAAA" + + "TAQAAFIEAABbBAAAXgQAAGMEAABmBAAAaQQAAAEAAAACAAAAAwAAAAQAAAAJAAAACgAAAAsAAAAM" + + "AAAADQAAAA4AAAAPAAAAEgAAABUAAAAFAAAABQAAAPgCAAAGAAAABgAAAAADAAAHAAAABwAAAAgD" + + "AAAIAAAACAAAABADAAASAAAACwAAAAAAAAATAAAACwAAAAgDAAAUAAAACwAAABgDAAAEAAIAIwAA" + + "AAQABQAAAAAABAAGABYAAAAEAAUAFwAAAAUAAAAeAAAABgABAB4AAAAHAAIAHgAAAAgAAwAeAAAA" + + "CQAEAAAAAAAKAAQAAAAAAAQAAAABAAAACQAAAAAAAAARAAAAAAAAAL4EAAAAAAAAAwACAAEAAABu" + + "BAAACAAAAHAQBwABANoAAgRZEAAADgABAAEAAQAAAHsEAAAEAAAAcRABAAAADgAQAAIAAgAAAIEE" + + "AABcAAAAEhkjmQwAEgpNDgkKcRABAAkA2AUPAxIIkgkFDzWYJACRCQUIgpYYCjMzMzMzM9M/iWyt" + + "AAoMEikjmQwAEgpxEAQABgAMC00LCQoSGnEgAwAQAAwLTQsJCnEQAQAJANgICAEo2yIDCQBwEAcA" + + "AwAiAgoAcBAIAAIA3gQPBNoJBAOBlhJJI5kMABIKcRAFAAQADAtNCwkKEhpxIAYAdgAMC00LCQoS" + + "Kk0DCQoSOk0CCQpxEAEACQAOAAEAAAAAAAAAAQAAAAEAAAABAAAAAgAAAAEAAAADAAAAAQAAAAwA" + + "Bjxpbml0PgABRAABRgABSQABSgACTEQAAkxGAAJMSQACTEoADExhcnQvVGFyZ2V0OwASTGphdmEv" + + "bGFuZy9Eb3VibGU7ABFMamF2YS9sYW5nL0Zsb2F0OwATTGphdmEvbGFuZy9JbnRlZ2VyOwAQTGph" + + "dmEvbGFuZy9Mb25nOwASTGphdmEvbGFuZy9PYmplY3Q7ABVMamF2YS91dGlsL0FycmF5TGlzdDsA" + + "KkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdlcjs+OwALVGFyZ2V0LmphdmEA" + + "AVYAAlZJAAJWTAATW0xqYXZhL2xhbmcvT2JqZWN0OwAJZG9Ob3RoaW5nAAtkb1NvbWV0aGluZwAB" + + "aQABbwAEb2JqcwABcAABcQAEdGhpcwAHdmFsdWVPZgABeAADeHh4AAF5AAF6AAN6enoABQEhBw48" + + "LQMAHQMtAAkBGwcOAAsBIAcOli0DBSIDAQEDCCMDSzwDBh0ChwMAGQEBFAtABQAFBloDAxoKWgQC" + + "GQsRLQMEHAM8AwYdBAEaDwAAAQIBAAEAgYAE+AMBiQGYBAIBsAQADQAAAAAAAAABAAAAAAAAAAEA" + + "AAAkAAAAcAAAAAIAAAANAAAAAAEAAAMAAAAHAAAANAEAAAQAAAABAAAAiAEAAAUAAAAJAAAAkAEA" + + "AAYAAAABAAAA2AEAAAEgAAADAAAA+AEAAAEQAAAFAAAA+AIAAAIgAAAkAAAAHgMAAAMgAAADAAAA" + + "bgQAAAAgAAABAAAAvgQAAAAQAAABAAAA1AQAAA=="); + + + // The variables of the functions in the above Target class. + public static Set<Locals.VariableDescription>[] CONSTRUCTOR_VARIABLES = new Set[] { + // RI Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(8, 6, "q", "I", null, 2), + new Locals.VariableDescription(0, 14, "xxx", "I", null, 1), + new Locals.VariableDescription(0, 14, "this", "Lart/Target;", null, 0))), + // ART Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 8, "this", "Lart/Target;", null, 1), + new Locals.VariableDescription(5, 3, "q", "I", null, 0), + new Locals.VariableDescription(0, 8, "xxx", "I", null, 2))), + }; + + public static Set<Locals.VariableDescription>[] DO_NOTHING_VARIABLES = new Set[] { + // RI Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 5, "objs", "[Ljava/lang/Object;", null, 0))), + // ART Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 4, "objs", "[Ljava/lang/Object;", null, 0))), + }; + + public static Set<Locals.VariableDescription>[] DO_SOMETHING_VARIABLES = new Set[] { + // RI Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 130, "x", "I", null, 1), + new Locals.VariableDescription(76, 54, "o", "Ljava/lang/Object;", null, 3), + new Locals.VariableDescription(30, 32, "q", "F", null, 4), + new Locals.VariableDescription(39, 23, "i", "D", null, 5), + new Locals.VariableDescription(17, 51, "z", "I", null, 3), + new Locals.VariableDescription(15, 115, "y", "I", null, 2), + new Locals.VariableDescription(90, 40, "p", "I", null, 5), + new Locals.VariableDescription(97, 33, "q", "J", null, 6), + new Locals.VariableDescription(0, 130, "this", "Lart/Target;", null, 0), + new Locals.VariableDescription(85, + 45, + "i", + "Ljava/util/ArrayList;", + "Ljava/util/ArrayList<Ljava/lang/Integer;>;", + 4))), + // ART Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(19, 31, "q", "F", null, 6), + new Locals.VariableDescription(55, 37, "o", "Ljava/lang/Object;", null, 3), + new Locals.VariableDescription(0, 92, "this", "Lart/Target;", null, 14), + new Locals.VariableDescription(12, 80, "z", "I", null, 8), + new Locals.VariableDescription(11, 81, "y", "I", null, 5), + new Locals.VariableDescription(62, 30, "p", "I", null, 4), + new Locals.VariableDescription(0, 92, "x", "I", null, 15), + new Locals.VariableDescription(27, 23, "i", "D", null, 0), + new Locals.VariableDescription(65, 27, "q", "J", null, 6), + new Locals.VariableDescription(60, + 32, + "i", + "Ljava/util/ArrayList;", + "Ljava/util/ArrayList<Ljava/lang/Integer;>;", + 2))), + }; + + // Get a classloader that can load the Target class. + public static ClassLoader getClassLoader() throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class); + // We are on art since we got the InMemoryDexClassLoader. + return (ClassLoader)ctor.newInstance( + ByteBuffer.wrap(DEX_BYTES), Test1911.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. + return new ClassLoader(Test1911.class.getClassLoader()) { + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals("art.Target")) { + return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length); + } else { + return super.findClass(name); + } + } + }; + } + } + + public static void CheckLocalVariableTable(Executable m, + Set<Locals.VariableDescription>[] possible_vars) { + Set<Locals.VariableDescription> real_vars = + new HashSet<>(Arrays.asList(Locals.GetLocalVariableTable(m))); + for (Set<Locals.VariableDescription> pos : possible_vars) { + if (pos.equals(real_vars)) { + return; + } + } + System.out.println("Unexpected variables for " + m); + System.out.println("Received: " + real_vars); + System.out.println("Expected one of:"); + for (Object pos : possible_vars) { + System.out.println("\t" + pos); + } + } + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + Class<?> target = getClassLoader().loadClass("art.Target"); + CheckLocalVariableTable(target.getDeclaredConstructor(Integer.TYPE), + CONSTRUCTOR_VARIABLES); + CheckLocalVariableTable(target.getDeclaredMethod("doNothing", (new Object[0]).getClass()), + DO_NOTHING_VARIABLES); + CheckLocalVariableTable(target.getDeclaredMethod("doSomething", Integer.TYPE), + DO_SOMETHING_VARIABLES); + } +} + diff --git a/test/1912-get-set-local-primitive/expected.txt b/test/1912-get-set-local-primitive/expected.txt new file mode 100644 index 0000000000..f2c5ce8640 --- /dev/null +++ b/test/1912-get-set-local-primitive/expected.txt @@ -0,0 +1,108 @@ +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) got value: 42 + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) set value: 2147483647 + Value is '2147483647' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) got value: 9001 + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) set value: 9223372036854775807 + Value is '9223372036854775807' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) got value: 1.618 + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) set value: 9.2 + Value is '9.2' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) got value: 3.1415 + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) set value: 12.4 + Value is '12.4' (class: class java.lang.Double) +Running public static void art.Test1912.BooleanMethod(java.lang.Runnable) with "SetIntBoolSize" on remote thread. +"SetIntBoolSize" on public static void art.Test1912.BooleanMethod(java.lang.Runnable) set value: 1 + Value is 'true' (class: class java.lang.Boolean) +Running public static void art.Test1912.ByteMethod(java.lang.Runnable) with "SetIntByteSize" on remote thread. +"SetIntByteSize" on public static void art.Test1912.ByteMethod(java.lang.Runnable) set value: 126 + Value is '126' (class: class java.lang.Byte) +Running public static void art.Test1912.CharMethod(java.lang.Runnable) with "SetIntCharSize" on remote thread. +"SetIntCharSize" on public static void art.Test1912.CharMethod(java.lang.Runnable) set value: 65534 + Value is '<Char: -1>' (class: class java.lang.String) +Running public static void art.Test1912.ShortMethod(java.lang.Runnable) with "SetIntShortSize" on remote thread. +"SetIntShortSize" on public static void art.Test1912.ShortMethod(java.lang.Runnable) set value: 32766 + Value is '32766' (class: class java.lang.Short) diff --git a/test/1912-get-set-local-primitive/info.txt b/test/1912-get-set-local-primitive/info.txt new file mode 100644 index 0000000000..87a7b35333 --- /dev/null +++ b/test/1912-get-set-local-primitive/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get/set Local variable primitives. + diff --git a/test/1912-get-set-local-primitive/run b/test/1912-get-set-local-primitive/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1912-get-set-local-primitive/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1912-get-set-local-primitive/src/Main.java b/test/1912-get-set-local-primitive/src/Main.java new file mode 100644 index 0000000000..9ff69f8a05 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1912.run(); + } +} diff --git a/test/1912-get-set-local-primitive/src/art/Breakpoint.java b/test/1912-get-set-local-primitive/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1912-get-set-local-primitive/src/art/Locals.java b/test/1912-get-set-local-primitive/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1912-get-set-local-primitive/src/art/StackTrace.java b/test/1912-get-set-local-primitive/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1912-get-set-local-primitive/src/art/Suspension.java b/test/1912-get-set-local-primitive/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1912-get-set-local-primitive/src/art/Test1912.java b/test/1912-get-set-local-primitive/src/art/Test1912.java new file mode 100644 index 0000000000..24149f4cbe --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Test1912.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +// TODO Rename test to set-get-local-prim + +public class Test1912 { + public static final String TARGET_VAR = "TARGET"; + + + public static void reportValue(Object val) { + if (val instanceof Character) { + val = "<Char: " + Character.getNumericValue(((Character)val).charValue()) + ">"; + } + System.out.println("\tValue is '" + val + "' (class: " + val.getClass() + ")"); + } + + public static void BooleanMethod(Runnable safepoint) { + boolean TARGET = false; + safepoint.run(); + reportValue(TARGET); + } + public static void ByteMethod(Runnable safepoint) { + byte TARGET = 8; + safepoint.run(); + reportValue(TARGET); + } + public static void CharMethod(Runnable safepoint) { + char TARGET = 'q'; + safepoint.run(); + reportValue(TARGET); + } + public static void ShortMethod(Runnable safepoint) { + short TARGET = 321; + safepoint.run(); + reportValue(TARGET); + } + public static void IntMethod(Runnable safepoint) { + int TARGET = 42; + safepoint.run(); + reportValue(TARGET); + } + public static void LongMethod(Runnable safepoint) { + long TARGET = 9001; + safepoint.run(); + reportValue(TARGET); + } + public static void FloatMethod(Runnable safepoint) { + float TARGET = 1.618f; + safepoint.run(); + reportValue(TARGET); + } + public static void DoubleMethod(Runnable safepoint) { + double TARGET = 3.1415d; + safepoint.run(); + reportValue(TARGET); + } + + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + Locals.VariableDescription TARGET_desc, + int depth) throws Exception; + } + + public static interface SetterFunction { + public void SetVar(Thread t, int depth, int slot, Object v); + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth, int slot); + } + + public static SafepointFunction NamedSet( + final String type, final SetterFunction get, final Object v) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + get.SetVar(t, depth, desc.slot, v); + System.out.println(this + " on " + method + " set value: " + v); + } catch (Exception e) { + System.out.println( + this + " on " + method + " failed to set value " + v + " due to " + e.getMessage()); + } + } + public String toString() { + return "\"Set" + type + "\""; + } + }; + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + Object res = get.GetVar(t, depth, desc.slot); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Method target; + + public TestCase(Method target) { + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(null, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + Locals.VariableDescription desc = findTargetVar(frame.current_location); + safepoint.invoke(remote, target, desc, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private Locals.VariableDescription findTargetVar(long loc) { + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc); + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + public static Method getMethod(String name) throws Exception { + return Test1912.class.getDeclaredMethod(name, Runnable.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(getMethod("IntMethod")), + new TestCase(getMethod("LongMethod")), + new TestCase(getMethod("FloatMethod")), + new TestCase(getMethod("DoubleMethod")), + }; + + final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] { + NamedGet("Int", Locals::GetLocalVariableInt), + NamedGet("Long", Locals::GetLocalVariableLong), + NamedGet("Float", Locals::GetLocalVariableFloat), + NamedGet("Double", Locals::GetLocalVariableDouble), + NamedSet("Int", Locals::SetLocalVariableInt, Integer.MAX_VALUE), + NamedSet("Long", Locals::SetLocalVariableLong, Long.MAX_VALUE), + NamedSet("Float", Locals::SetLocalVariableFloat, 9.2f), + NamedSet("Double", Locals::SetLocalVariableDouble, 12.4d), + }; + + for (TestCase t: MAIN_TEST_CASES) { + for (SafepointFunction s : SAFEPOINTS) { + t.exec(s); + } + } + + // Test int for small values. + new TestCase(getMethod("BooleanMethod")).exec( + NamedSet("IntBoolSize", Locals::SetLocalVariableInt, 1)); + new TestCase(getMethod("ByteMethod")).exec( + NamedSet("IntByteSize", Locals::SetLocalVariableInt, Byte.MAX_VALUE - 1)); + + new TestCase(getMethod("CharMethod")).exec( + NamedSet("IntCharSize", Locals::SetLocalVariableInt, Character.MAX_VALUE - 1)); + new TestCase(getMethod("ShortMethod")).exec( + NamedSet("IntShortSize", Locals::SetLocalVariableInt, Short.MAX_VALUE - 1)); + } +} + diff --git a/test/1913-get-set-local-objects/expected.txt b/test/1913-get-set-local-objects/expected.txt new file mode 100644 index 0000000000..23f49920c9 --- /dev/null +++ b/test/1913-get-set-local-objects/expected.txt @@ -0,0 +1,72 @@ +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) got value: TestClass1("ObjectMethod") + Value is 'TestClass1("ObjectMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: null + Value is 'null' (class: NULL) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1") + Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")") + Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2("Set TestClass2") + Value is 'TestClass2("Set TestClass2")' (class: class art.Test1913$TestClass2) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")") + Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) got value: TestClass1("InterfaceMethod") + Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: null + Value is 'null' (class: NULL) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1") + Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")") + Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH + Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")") + Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) got value: TestClass1("SpecificClassMethod") + Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: null + Value is 'null' (class: NULL) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1") + Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")") + Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH + Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH + Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value null due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1("Set TestClass1") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1ext("TestClass1("Set TestClass1ext")") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) diff --git a/test/1913-get-set-local-objects/info.txt b/test/1913-get-set-local-objects/info.txt new file mode 100644 index 0000000000..86ac7435ec --- /dev/null +++ b/test/1913-get-set-local-objects/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get and set local variable object. + diff --git a/test/1913-get-set-local-objects/run b/test/1913-get-set-local-objects/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1913-get-set-local-objects/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1913-get-set-local-objects/src/Main.java b/test/1913-get-set-local-objects/src/Main.java new file mode 100644 index 0000000000..45565c2897 --- /dev/null +++ b/test/1913-get-set-local-objects/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1913.run(); + } +} diff --git a/test/1913-get-set-local-objects/src/art/Breakpoint.java b/test/1913-get-set-local-objects/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1913-get-set-local-objects/src/art/Locals.java b/test/1913-get-set-local-objects/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1913-get-set-local-objects/src/art/StackTrace.java b/test/1913-get-set-local-objects/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1913-get-set-local-objects/src/art/Suspension.java b/test/1913-get-set-local-objects/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1913-get-set-local-objects/src/art/Test1913.java b/test/1913-get-set-local-objects/src/art/Test1913.java new file mode 100644 index 0000000000..417138a194 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Test1913.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1913 { + public static final String TARGET_VAR = "TARGET"; + + public static interface TestInterface { + public default void doNothing() {} + } + public static class TestClass1 implements TestInterface { + public String id; + public TestClass1(String id) { this.id = id; } + public String toString() { return String.format("TestClass1(\"%s\")", id); } + } + + public static class TestClass1ext extends TestClass1 { + public TestClass1ext(String id) { super(id); } + public String toString() { return String.format("TestClass1ext(\"%s\")", super.toString()); } + } + public static class TestClass2 { + public String id; + public TestClass2(String id) { this.id = id; } + public String toString() { return String.format("TestClass2(\"%s\")", id); } + } + public static class TestClass2impl extends TestClass2 implements TestInterface { + public TestClass2impl(String id) { super(id); } + public String toString() { return String.format("TestClass2impl(\"%s\")", super.toString()); } + } + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "' (class: " + + (val != null ? val.getClass() : "NULL") + ")"); + } + + public static void PrimitiveMethod(Runnable safepoint) { + int TARGET = 42; + safepoint.run(); + reportValue(TARGET); + } + + // b/64115302: Needed to make sure that DX doesn't change the type of TARGET to TestClass1. + private static Object AsObject(Object o) { return o; } + public static void ObjectMethod(Runnable safepoint) { + Object TARGET = AsObject(new TestClass1("ObjectMethod")); + safepoint.run(); + reportValue(TARGET); + } + + public static void InterfaceMethod(Runnable safepoint) { + TestInterface TARGET = new TestClass1("InterfaceMethod"); + safepoint.run(); + reportValue(TARGET); + } + + public static void SpecificClassMethod(Runnable safepoint) { + TestClass1 TARGET = new TestClass1("SpecificClassMethod"); + safepoint.run(); + reportValue(TARGET); + } + + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + Locals.VariableDescription TARGET_desc, + int depth) throws Exception; + } + + public static interface SetterFunction { + public void SetVar(Thread t, int depth, int slot, Object v); + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth, int slot); + } + + public static SafepointFunction NamedSet( + final String type, final SetterFunction get, final Object v) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + get.SetVar(t, depth, desc.slot, v); + System.out.println(this + " on " + method + " set value: " + v); + } catch (Exception e) { + System.out.println( + this + " on " + method + " failed to set value " + v + " due to " + e.getMessage()); + } + } + public String toString() { + return "\"Set" + type + "\""; + } + }; + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + Object res = get.GetVar(t, depth, desc.slot); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Method target; + + public TestCase(Method target) { + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(null, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + Locals.VariableDescription desc = findTargetVar(frame.current_location); + safepoint.invoke(remote, target, desc, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private Locals.VariableDescription findTargetVar(long loc) { + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc); + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + public static Method getMethod(String name) throws Exception { + return Test1913.class.getDeclaredMethod(name, Runnable.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(getMethod("ObjectMethod")), + new TestCase(getMethod("InterfaceMethod")), + new TestCase(getMethod("SpecificClassMethod")), + new TestCase(getMethod("PrimitiveMethod")), + }; + + final SetterFunction set_obj = Locals::SetLocalVariableObject; + final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] { + NamedGet("GetObject", Locals::GetLocalVariableObject), + NamedSet("Null", set_obj, null), + NamedSet("TestClass1", set_obj, new TestClass1("Set TestClass1")), + NamedSet("TestClass1ext", set_obj, new TestClass1ext("Set TestClass1ext")), + NamedSet("TestClass2", set_obj, new TestClass2("Set TestClass2")), + NamedSet("TestClass2impl", set_obj, new TestClass2impl("Set TestClass2impl")), + }; + + for (TestCase t: MAIN_TEST_CASES) { + for (SafepointFunction s : SAFEPOINTS) { + t.exec(s); + } + } + } +} + diff --git a/test/1914-get-local-instance/expected.txt b/test/1914-get-local-instance/expected.txt new file mode 100644 index 0000000000..4117942392 --- /dev/null +++ b/test/1914-get-local-instance/expected.txt @@ -0,0 +1,12 @@ +Running public static void art.Test1914.StaticMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public static void art.Test1914.StaticMethod(java.lang.Runnable) got value: null + Value is 'null' (class: NULL) +Running public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) got value: null + Value is 'null' (class: NULL) +Running public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) got value: TargetClass("InstanceMethodObject") + Value is 'TargetClass("InstanceMethodObject")' (class: class art.Test1914$TargetClass) +Running public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) got value: TargetClass("NativeInstanceMethodObject") + Value is 'TargetClass("NativeInstanceMethodObject")' (class: class art.Test1914$TargetClass) diff --git a/test/1914-get-local-instance/info.txt b/test/1914-get-local-instance/info.txt new file mode 100644 index 0000000000..9fc3d62cd6 --- /dev/null +++ b/test/1914-get-local-instance/info.txt @@ -0,0 +1,2 @@ +Test for jvmti get local instance + diff --git a/test/1914-get-local-instance/local_instance.cc b/test/1914-get-local-instance/local_instance.cc new file mode 100644 index 0000000000..03aa59e484 --- /dev/null +++ b/test/1914-get-local-instance/local_instance.cc @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1914LocalInstance { + +extern "C" JNIEXPORT void Java_art_Test1914_00024TargetClass_NativeInstanceMethod( + JNIEnv* env, jobject thiz, jobject run) { + ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable")); + if (env->ExceptionCheck()) { return; } + jmethodID method = env->GetMethodID(runnable.get(), "run", "()V"); + if (env->ExceptionCheck()) { return; } + env->CallVoidMethod(run, method); + if (env->ExceptionCheck()) { return; } + ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914")); + if (env->ExceptionCheck()) { return; } + jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V"); + if (env->ExceptionCheck()) { return; } + env->CallStaticVoidMethod(Test1914.get(), report, thiz); +} + +extern "C" JNIEXPORT void Java_art_Test1914_NativeStaticMethod( + JNIEnv* env, jclass, jobject run) { + ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable")); + if (env->ExceptionCheck()) { return; } + jmethodID method = env->GetMethodID(runnable.get(), "run", "()V"); + if (env->ExceptionCheck()) { return; } + env->CallVoidMethod(run, method); + if (env->ExceptionCheck()) { return; } + ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914")); + if (env->ExceptionCheck()) { return; } + jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V"); + if (env->ExceptionCheck()) { return; } + env->CallStaticVoidMethod(Test1914.get(), report, nullptr); +} + +} // namespace Test1914LocalInstance +} // namespace art + diff --git a/test/1914-get-local-instance/run b/test/1914-get-local-instance/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1914-get-local-instance/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1914-get-local-instance/src/Main.java b/test/1914-get-local-instance/src/Main.java new file mode 100644 index 0000000000..163221e12f --- /dev/null +++ b/test/1914-get-local-instance/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1914.run(); + } +} diff --git a/test/1914-get-local-instance/src/art/Breakpoint.java b/test/1914-get-local-instance/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1914-get-local-instance/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1914-get-local-instance/src/art/Locals.java b/test/1914-get-local-instance/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1914-get-local-instance/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1914-get-local-instance/src/art/StackTrace.java b/test/1914-get-local-instance/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1914-get-local-instance/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1914-get-local-instance/src/art/Suspension.java b/test/1914-get-local-instance/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1914-get-local-instance/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1914-get-local-instance/src/art/Test1914.java b/test/1914-get-local-instance/src/art/Test1914.java new file mode 100644 index 0000000000..c09f519db8 --- /dev/null +++ b/test/1914-get-local-instance/src/art/Test1914.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1914 { + public static final String TARGET_VAR = "TARGET"; + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "' (class: " + + (val != null ? val.getClass() : "NULL") + ")"); + } + + public static void StaticMethod(Runnable safepoint) { + safepoint.run(); + reportValue(null); + } + + public static native void NativeStaticMethod(Runnable safepoint); + + public static class TargetClass { + public String id; + public String toString() { return String.format("TargetClass(\"%s\")", id); } + public TargetClass(String id) { this.id = id; } + + public void InstanceMethod(Runnable safepoint) { + safepoint.run(); + reportValue(this); + } + + public native void NativeInstanceMethod(Runnable safepoint); + } + + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + int depth) throws Exception; + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth); + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, int depth) { + try { + Object res = get.GetVar(t, depth); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Object thiz; + public final Method target; + + public TestCase(Method target) { + this(null, target); + } + public TestCase(Object thiz, Method target) { + this.thiz = thiz; + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(thiz, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + safepoint.invoke(remote, target, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + + public static Method getMethod(Class<?> klass, String name) throws Exception { + return klass.getDeclaredMethod(name, Runnable.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(null, getMethod(Test1914.class, "StaticMethod")), + new TestCase(null, getMethod(Test1914.class, "NativeStaticMethod")), + new TestCase(new TargetClass("InstanceMethodObject"), + getMethod(TargetClass.class, "InstanceMethod")), + new TestCase(new TargetClass("NativeInstanceMethodObject"), + getMethod(TargetClass.class, "NativeInstanceMethod")), + }; + + for (TestCase t: MAIN_TEST_CASES) { + t.exec(NamedGet("This", Locals::GetLocalInstance)); + } + } +} + diff --git a/test/1915-get-set-local-current-thread/expected.txt b/test/1915-get-set-local-current-thread/expected.txt new file mode 100644 index 0000000000..de39ca9554 --- /dev/null +++ b/test/1915-get-set-local-current-thread/expected.txt @@ -0,0 +1,5 @@ +GetLocalInt on current thread! +From GetLocalInt(), value is 42 + Value is '42' +SetLocalInt on current thread! + Value is '1337' diff --git a/test/1915-get-set-local-current-thread/info.txt b/test/1915-get-set-local-current-thread/info.txt new file mode 100644 index 0000000000..c59f50de3b --- /dev/null +++ b/test/1915-get-set-local-current-thread/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get/set Local variable on current thread. + diff --git a/test/1915-get-set-local-current-thread/run b/test/1915-get-set-local-current-thread/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1915-get-set-local-current-thread/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1915-get-set-local-current-thread/src/Main.java b/test/1915-get-set-local-current-thread/src/Main.java new file mode 100644 index 0000000000..47e676704f --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1915.run(); + } +} diff --git a/test/1915-get-set-local-current-thread/src/art/Breakpoint.java b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1915-get-set-local-current-thread/src/art/Locals.java b/test/1915-get-set-local-current-thread/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1915-get-set-local-current-thread/src/art/StackTrace.java b/test/1915-get-set-local-current-thread/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1915-get-set-local-current-thread/src/art/Suspension.java b/test/1915-get-set-local-current-thread/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1915-get-set-local-current-thread/src/art/Test1915.java b/test/1915-get-set-local-current-thread/src/art/Test1915.java new file mode 100644 index 0000000000..a99a487ea6 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Test1915.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1915 { + public static final int SET_VALUE = 1337; + public static final String TARGET_VAR = "TARGET"; + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "'"); + } + public static interface ThrowRunnable { + public void run() throws Exception; + } + + public static void IntMethod(ThrowRunnable safepoint) throws Exception { + int TARGET = 42; + safepoint.run(); + reportValue(TARGET); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final Method target = Test1915.class.getDeclaredMethod("IntMethod", ThrowRunnable.class); + // Get Variable. + System.out.println("GetLocalInt on current thread!"); + IntMethod(() -> { + StackTrace.StackFrameData frame = FindStackFrame(target); + int depth = FindExpectedFrameDepth(frame); + int slot = FindSlot(frame); + int value = Locals.GetLocalVariableInt(Thread.currentThread(), depth, slot); + System.out.println("From GetLocalInt(), value is " + value); + }); + // Set Variable. + System.out.println("SetLocalInt on current thread!"); + IntMethod(() -> { + StackTrace.StackFrameData frame = FindStackFrame(target); + int depth = FindExpectedFrameDepth(frame); + int slot = FindSlot(frame); + Locals.SetLocalVariableInt(Thread.currentThread(), depth, slot, SET_VALUE); + }); + } + + public static int FindSlot(StackTrace.StackFrameData frame) throws Exception { + long loc = frame.current_location; + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var.slot; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc); + } + + public static int FindExpectedFrameDepth(StackTrace.StackFrameData frame) throws Exception { + // Adjust the 'frame' depth since it is modified by: + // +1 for Get/SetLocalVariableInt in future. + // -1 for FindStackFrame + // -1 for GetStackTrace + // -1 for GetStackTraceNative + // ------------------------------ + // -2 + return frame.depth - 2; + } + + private static StackTrace.StackFrameData FindStackFrame(Method target) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target); + } +} + diff --git a/test/1916-get-set-current-frame/expected.txt b/test/1916-get-set-current-frame/expected.txt new file mode 100644 index 0000000000..343d49310e --- /dev/null +++ b/test/1916-get-set-current-frame/expected.txt @@ -0,0 +1,4 @@ +From GetLocalInt(), value is 42 + Value is '42' +Setting TARGET to 1337 + Value is '1337' diff --git a/test/1916-get-set-current-frame/info.txt b/test/1916-get-set-current-frame/info.txt new file mode 100644 index 0000000000..7342af7e71 --- /dev/null +++ b/test/1916-get-set-current-frame/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get/set Local variable in the currently executing method frame. + diff --git a/test/1916-get-set-current-frame/run b/test/1916-get-set-current-frame/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1916-get-set-current-frame/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1916-get-set-current-frame/src/Main.java b/test/1916-get-set-current-frame/src/Main.java new file mode 100644 index 0000000000..7d0cd21772 --- /dev/null +++ b/test/1916-get-set-current-frame/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1916.run(); + } +} diff --git a/test/1916-get-set-current-frame/src/art/Breakpoint.java b/test/1916-get-set-current-frame/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1916-get-set-current-frame/src/art/Locals.java b/test/1916-get-set-current-frame/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1916-get-set-current-frame/src/art/StackTrace.java b/test/1916-get-set-current-frame/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1916-get-set-current-frame/src/art/Suspension.java b/test/1916-get-set-current-frame/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1916-get-set-current-frame/src/art/Test1916.java b/test/1916-get-set-current-frame/src/art/Test1916.java new file mode 100644 index 0000000000..3e5bce2619 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Test1916.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1916 { + public static final int SET_VALUE = 1337; + public static final String TARGET_VAR = "TARGET"; + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "'"); + } + + public static class IntRunner implements Runnable { + private volatile boolean continueBusyLoop; + private volatile boolean inBusyLoop; + public IntRunner() { + this.continueBusyLoop = true; + this.inBusyLoop = false; + } + public void run() { + int TARGET = 42; + // We will suspend the thread during this loop. + while (continueBusyLoop) { + inBusyLoop = true; + } + reportValue(TARGET); + } + public void waitForBusyLoopStart() { while (!inBusyLoop) {} } + public void finish() { continueBusyLoop = false; } + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + runGet(); + runSet(); + } + + public static void runGet() throws Exception { + Method target = IntRunner.class.getDeclaredMethod("run"); + // Get Int + IntRunner int_runner = new IntRunner(); + Thread target_get = new Thread(int_runner, "GetLocalInt - Target"); + target_get.start(); + int_runner.waitForBusyLoopStart(); + try { + Suspension.suspend(target_get); + } catch (Exception e) { + System.out.println("FAIL: got " + e); + e.printStackTrace(); + int_runner.finish(); + target_get.join(); + return; + } + try { + StackTrace.StackFrameData frame = FindStackFrame(target_get, target); + int depth = frame.depth; + if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); } + int slot = FindSlot(frame); + int value = Locals.GetLocalVariableInt(target_get, depth, slot); + System.out.println("From GetLocalInt(), value is " + value); + } finally { + Suspension.resume(target_get); + int_runner.finish(); + target_get.join(); + } + } + + public static void runSet() throws Exception { + Method target = IntRunner.class.getDeclaredMethod("run"); + // Set Int + IntRunner int_runner = new IntRunner(); + Thread target_set = new Thread(int_runner, "SetLocalInt - Target"); + target_set.start(); + int_runner.waitForBusyLoopStart(); + try { + Suspension.suspend(target_set); + } catch (Exception e) { + System.out.println("FAIL: got " + e); + e.printStackTrace(); + int_runner.finish(); + target_set.join(); + return; + } + try { + StackTrace.StackFrameData frame = FindStackFrame(target_set, target); + int depth = frame.depth; + if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); } + int slot = FindSlot(frame); + System.out.println("Setting TARGET to " + SET_VALUE); + Locals.SetLocalVariableInt(target_set, depth, slot, SET_VALUE); + } finally { + Suspension.resume(target_set); + int_runner.finish(); + target_set.join(); + } + } + + public static int FindSlot(StackTrace.StackFrameData frame) throws Exception { + long loc = frame.current_location; + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var.slot; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc); + } + + private static StackTrace.StackFrameData FindStackFrame(Thread thr, Method target) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } +} + diff --git a/test/1917-get-stack-frame/expected.txt b/test/1917-get-stack-frame/expected.txt new file mode 100644 index 0000000000..26217e678e --- /dev/null +++ b/test/1917-get-stack-frame/expected.txt @@ -0,0 +1,33 @@ +Recurring 5 times +'private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)' line: -1 +'public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)' line: 60 +'public void art.Test1917$StackTraceGenerator.run()' line: 81 +'public void art.Test1917$RecurCount.doRecur(int)' line: 103 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.run()' line: 96 +'public static void art.Test1917.run() throws java.lang.Exception' line: 132 +Recurring 5 times on another thread +'private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)' line: -1 +'public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)' line: 60 +'public void art.Test1917$StackTraceGenerator.run()' line: 81 +'public void art.Test1917$RecurCount.doRecur(int)' line: 103 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.run()' line: 96 +Recurring 5 times on another thread. Stack trace from main thread! +'public void java.util.concurrent.Semaphore.acquire() throws java.lang.InterruptedException' line: <NOT-DETERMINISTIC> +'public void art.Test1917$ThreadPauser.run()' line: 46 +'public void art.Test1917$RecurCount.doRecur(int)' line: 103 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.doRecur(int)' line: 101 +'public void art.Test1917$RecurCount.run()' line: 96 diff --git a/test/1917-get-stack-frame/info.txt b/test/1917-get-stack-frame/info.txt new file mode 100644 index 0000000000..e72034adef --- /dev/null +++ b/test/1917-get-stack-frame/info.txt @@ -0,0 +1 @@ +Tests stack frame functions of jvmti diff --git a/test/1917-get-stack-frame/run b/test/1917-get-stack-frame/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1917-get-stack-frame/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1917-get-stack-frame/src/Main.java b/test/1917-get-stack-frame/src/Main.java new file mode 100644 index 0000000000..c055a5c540 --- /dev/null +++ b/test/1917-get-stack-frame/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1917.run(); + } +} diff --git a/test/1917-get-stack-frame/src/art/Breakpoint.java b/test/1917-get-stack-frame/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1917-get-stack-frame/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1917-get-stack-frame/src/art/StackTrace.java b/test/1917-get-stack-frame/src/art/StackTrace.java new file mode 100644 index 0000000000..b12c3df66b --- /dev/null +++ b/test/1917-get-stack-frame/src/art/StackTrace.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1917-get-stack-frame/src/art/Suspension.java b/test/1917-get-stack-frame/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1917-get-stack-frame/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1917-get-stack-frame/src/art/Test1917.java b/test/1917-get-stack-frame/src/art/Test1917.java new file mode 100644 index 0000000000..1057235426 --- /dev/null +++ b/test/1917-get-stack-frame/src/art/Test1917.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.Vector; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1917 { + public final static boolean TEST_PRINT_ALL = false; + + public static class ThreadPauser implements Runnable { + public Semaphore sem_wakeup_main = new Semaphore(0); + public Semaphore sem_wait = new Semaphore(0); + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public static class StackTraceGenerator implements Runnable { + private final Thread thr; + private final Consumer<StackTrace.StackFrameData> con; + public StackTraceGenerator(Thread thr, Consumer<StackTrace.StackFrameData> con) { + this.thr = thr; + this.con = con; + } + + public StackTraceGenerator(Consumer<StackTrace.StackFrameData> con) { + this(null, con); + } + + public Thread getThread() { + if (thr == null) { + return Thread.currentThread(); + } else { + return thr; + } + } + public void run() { + for (StackTrace.StackFrameData s : StackTrace.GetStackTrace(getThread())) { + con.accept(s); + } + } + } + + public static class RecurCount implements Runnable { + private final int cnt; + private final Runnable then; + public RecurCount(int cnt, Runnable then) { + this.cnt = cnt; + this.then = then; + } + + public void run() { + doRecur(0); + } + + public void doRecur(int n) { + if (n < cnt) { + doRecur(n + 1); + } else { + then.run(); + } + } + } + + public static Consumer<StackTrace.StackFrameData> makePrintStackFramesConsumer() + throws Exception { + final Method end_method = Test1917.class.getDeclaredMethod("run"); + return new Consumer<StackTrace.StackFrameData>() { + public void accept(StackTrace.StackFrameData data) { + if (TEST_PRINT_ALL) { + System.out.println(data); + } else { + Package p = data.method.getDeclaringClass().getPackage(); + // Filter out anything to do with the testing harness. + if (p != null && p.equals(Test1917.class.getPackage())) { + System.out.printf("'%s' line: %d\n", + data.method, + Breakpoint.locationToLine(data.method, data.current_location)); + } else if (data.method.getDeclaringClass().equals(Semaphore.class)) { + System.out.printf("'%s' line: <NOT-DETERMINISTIC>\n", data.method); + } + } + } + }; + } + + public static void run() throws Exception { + System.out.println("Recurring 5 times"); + new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())).run(); + + System.out.println("Recurring 5 times on another thread"); + Thread thr = new Thread( + new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer()))); + thr.start(); + thr.join(); + + System.out.println("Recurring 5 times on another thread. Stack trace from main thread!"); + ThreadPauser pause = new ThreadPauser(); + Thread thr2 = new Thread(new RecurCount(5, pause)); + thr2.start(); + pause.waitForOtherThreadToPause(); + new StackTraceGenerator(thr2, makePrintStackFramesConsumer()).run(); + pause.wakeupOtherThread(); + thr2.join(); + } +} diff --git a/test/Android.bp b/test/Android.bp index 44cb4f6e8a..fab664a3e2 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -252,8 +252,10 @@ art_cc_defaults { "ti-agent/test_env.cc", "ti-agent/breakpoint_helper.cc", "ti-agent/common_helper.cc", + "ti-agent/locals_helper.cc", "ti-agent/redefinition_helper.cc", "ti-agent/suspension_helper.cc", + "ti-agent/stack_trace_helper.cc", "ti-agent/trace_helper.cc", // This is the list of non-special OnLoad things and excludes BCI and anything that depends // on ART internals. @@ -292,6 +294,7 @@ art_cc_defaults { "1905-suspend-native/native_suspend.cc", "1908-suspend-native-resume-self/native_suspend_resume.cc", "1909-per-agent-tls/agent_tls.cc", + "1914-get-local-instance/local_instance.cc", ], shared_libs: [ "libbase", diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index ede485a81b..e989e39bf3 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -453,6 +453,10 @@ fi if [ "$USE_JVM" = "y" ]; then export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64 + # Some jvmti tests are flaky without -Xint on the RI. + if [ "$IS_JVMTI_TEST" = "y" ]; then + FLAGS="${FLAGS} -Xint" + fi # Xmx is necessary since we don't pass down the ART flags to JVM. # We pass the classes2 path whether it's used (src-multidex) or not. cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}" diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc index 51d3406a35..0d5cb39985 100644 --- a/test/ti-agent/jvmti_helper.cc +++ b/test/ti-agent/jvmti_helper.cc @@ -15,6 +15,7 @@ */ #include "jvmti_helper.h" +#include "test_env.h" #include <dlfcn.h> @@ -90,6 +91,11 @@ jvmtiCapabilities GetStandardCapabilities() { } void SetStandardCapabilities(jvmtiEnv* env) { + if (IsJVM()) { + // RI is more strict about adding capabilities at runtime then ART so just give it everything. + SetAllCapabilities(env); + return; + } jvmtiCapabilities caps = GetStandardCapabilities(); CheckJvmtiError(env, env->AddCapabilities(&caps)); } @@ -100,7 +106,7 @@ void SetAllCapabilities(jvmtiEnv* env) { CheckJvmtiError(env, env->AddCapabilities(&caps)); } -bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) { +bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error) { if (error == JVMTI_ERROR_NONE) { return false; } @@ -112,11 +118,11 @@ bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) { } char* err; - CheckJvmtiError(jvmti_env, jvmti_env->GetErrorName(error, &err)); + CheckJvmtiError(jvmtienv, jvmtienv->GetErrorName(error, &err)); env->ThrowNew(rt_exception.get(), err); - Deallocate(jvmti_env, err); + Deallocate(jvmtienv, err); return true; } diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h index 78d238a980..a47a4028a9 100644 --- a/test/ti-agent/jvmti_helper.h +++ b/test/ti-agent/jvmti_helper.h @@ -43,7 +43,7 @@ void CheckJvmtiError(jvmtiEnv* env, jvmtiError error); // Convert the given error to a RuntimeException with a message derived from the error. Returns // true on error, false if error is JVMTI_ERROR_NONE. -bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error); +bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error); class JvmtiDeleter { public: diff --git a/test/ti-agent/locals_helper.cc b/test/ti-agent/locals_helper.cc new file mode 100644 index 0000000000..e284b52846 --- /dev/null +++ b/test/ti-agent/locals_helper.cc @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace common_locals { + +static void DeallocateContents(jvmtiLocalVariableEntry* vars, jint nvars) { + for (jint i = 0; i < nvars; i++) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].name)); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].signature)); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].generic_signature)); + } +} + +extern "C" JNIEXPORT void Java_art_Locals_EnableLocalVariableAccess(JNIEnv* env, jclass) { + jvmtiCapabilities caps; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCapabilities(&caps))) { + return; + } + caps.can_access_local_variables = 1; + JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableObject(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jobject val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalObject(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableDouble(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jdouble val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalDouble(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableFloat(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jfloat val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalFloat(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableLong(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jlong val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalLong(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableInt(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jint val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalInt(t, depth, slot, val)); +} + +extern "C" JNIEXPORT jdouble Java_art_Locals_GetLocalVariableDouble(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jdouble ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalDouble(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jfloat Java_art_Locals_GetLocalVariableFloat(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jfloat ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalFloat(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jlong Java_art_Locals_GetLocalVariableLong(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jlong ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalLong(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jint Java_art_Locals_GetLocalVariableInt(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jint ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInt(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalInstance(JNIEnv* env, + jclass, + jthread t, + jint depth) { + jobject ret = nullptr; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInstance(t, depth, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalVariableObject(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jobject ret = nullptr; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalObject(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobjectArray Java_art_Locals_GetLocalVariableTable(JNIEnv* env, + jclass, + jobject m) { + jmethodID method = env->FromReflectedMethod(m); + if (env->ExceptionCheck()) { + return nullptr; + } + ScopedLocalRef<jclass> klass(env, env->FindClass("art/Locals$VariableDescription")); + if (env->ExceptionCheck()) { + return nullptr; + } + jint nvars; + jvmtiLocalVariableEntry* vars = nullptr; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetLocalVariableTable(method, &nvars, &vars))) { + return nullptr; + } + jobjectArray vars_array = env->NewObjectArray(nvars, klass.get(), nullptr); + if (env->ExceptionCheck()) { + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return nullptr; + } + + jmethodID constructor = env->GetMethodID( + klass.get(), "<init>", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); + if (env->ExceptionCheck()) { + return nullptr; + } + for (jint i = 0; i < nvars; i++) { + ScopedLocalRef<jstring> name_string(env, env->NewStringUTF(vars[i].name)); + ScopedLocalRef<jstring> sig_string(env, env->NewStringUTF(vars[i].signature)); + ScopedLocalRef<jstring> generic_sig_string(env, env->NewStringUTF(vars[i].generic_signature)); + jobject var_obj = env->NewObject(klass.get(), + constructor, + vars[i].start_location, + vars[i].length, + name_string.get(), + sig_string.get(), + generic_sig_string.get(), + vars[i].slot); + if (env->ExceptionCheck()) { + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return nullptr; + } + env->SetObjectArrayElement(vars_array, i, var_obj); + if (env->ExceptionCheck()) { + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return nullptr; + } + } + + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return vars_array; +} + +} // namespace common_locals +} // namespace art diff --git a/test/ti-agent/stack_trace_helper.cc b/test/ti-agent/stack_trace_helper.cc new file mode 100644 index 0000000000..f2a8e9a318 --- /dev/null +++ b/test/ti-agent/stack_trace_helper.cc @@ -0,0 +1,99 @@ + +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace common_stack_trace { + +extern "C" JNIEXPORT jint JNICALL Java_art_StackTrace_GetStackDepth( + JNIEnv* env, jclass, jthread thr) { + jint ret; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobjectArray Java_art_StackTrace_nativeGetStackTrace(JNIEnv* env, + jclass, + jthread thr) { + jint depth; + ScopedLocalRef<jclass> klass(env, env->FindClass("art/StackTrace$StackFrameData")); + if (env->ExceptionCheck()) { + return nullptr; + } + jmethodID constructor = env->GetMethodID( + klass.get(), "<init>", "(Ljava/lang/Thread;Ljava/lang/reflect/Executable;JI)V"); + if (env->ExceptionCheck()) { + return nullptr; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &depth))) { + return nullptr; + } + // Just give some extra space. + depth += 10; + jvmtiFrameInfo* frames; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->Allocate(depth * sizeof(jvmtiFrameInfo), + reinterpret_cast<unsigned char**>(&frames)))) { + return nullptr; + } + jint nframes = 0; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetStackTrace(thr, 0, depth, frames, &nframes))) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + jobjectArray frames_array = env->NewObjectArray(nframes, klass.get(), nullptr); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + for (jint i = 0; i < nframes; i++) { + jobject jmethod = GetJavaMethod(jvmti_env, env, frames[i].method); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + jobject frame_obj = env->NewObject(klass.get(), + constructor, + thr, + jmethod, + frames[i].location, + i); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + env->SetObjectArrayElement(frames_array, i, frame_obj); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return frames_array; +} + +} // namespace common_stack_trace +} // namespace art |