diff options
Diffstat (limited to 'dalvik/src/main/java')
55 files changed, 6664 insertions, 0 deletions
diff --git a/dalvik/src/main/java/dalvik/annotation/AndroidOnly.java b/dalvik/src/main/java/dalvik/annotation/AndroidOnly.java new file mode 100644 index 0000000..da3c1c5 --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/AndroidOnly.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test-case as Android-only, that is, it should not be executed on + * other systems. + * + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface AndroidOnly { + + /** + * Plain text reason for adding this annotation. + */ + String value(); + +} diff --git a/dalvik/src/main/java/dalvik/annotation/AnnotationDefault.java b/dalvik/src/main/java/dalvik/annotation/AnnotationDefault.java new file mode 100644 index 0000000..98062fb --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/AnnotationDefault.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide the AnnotationDefault attribute. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface AnnotationDefault {} + diff --git a/dalvik/src/main/java/dalvik/annotation/BrokenTest.java b/dalvik/src/main/java/dalvik/annotation/BrokenTest.java new file mode 100644 index 0000000..401d652 --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/BrokenTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test case as broken. This means the test case should be fixed. + * + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface BrokenTest { + + /** + * Plain text reason for adding this annotation. + */ + String value(); + +} diff --git a/dalvik/src/main/java/dalvik/annotation/EnclosingClass.java b/dalvik/src/main/java/dalvik/annotation/EnclosingClass.java new file mode 100644 index 0000000..2546c4c --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/EnclosingClass.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide part of the InnerClasses attribute. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface EnclosingClass {} + diff --git a/dalvik/src/main/java/dalvik/annotation/EnclosingMethod.java b/dalvik/src/main/java/dalvik/annotation/EnclosingMethod.java new file mode 100644 index 0000000..1a97155 --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/EnclosingMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide the EnclosingMethod attribute. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface EnclosingMethod {} + diff --git a/dalvik/src/main/java/dalvik/annotation/InnerClass.java b/dalvik/src/main/java/dalvik/annotation/InnerClass.java new file mode 100644 index 0000000..054842f --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/InnerClass.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide part of the InnerClasses attribute. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface InnerClass {} + diff --git a/dalvik/src/main/java/dalvik/annotation/KnownFailure.java b/dalvik/src/main/java/dalvik/annotation/KnownFailure.java new file mode 100644 index 0000000..11e813d --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/KnownFailure.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test case as a known failure. This means the underlying + * implementation should be fixed. Seems to be similar to @code{@ToBeFixed}, so + * maybe the two can be merged at some point. + * + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface KnownFailure { + + /** + * Plain text reason for adding this annotation. + */ + String value(); + +} diff --git a/dalvik/src/main/java/dalvik/annotation/MemberClasses.java b/dalvik/src/main/java/dalvik/annotation/MemberClasses.java new file mode 100644 index 0000000..b213be7 --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/MemberClasses.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide the MemberClasses list. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface MemberClasses {} + diff --git a/dalvik/src/main/java/dalvik/annotation/SideEffect.java b/dalvik/src/main/java/dalvik/annotation/SideEffect.java new file mode 100644 index 0000000..b92e9bc --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/SideEffect.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test-case as either having a side-effect that other tests might + * notice or suffering from such a side effect. Such tests should be run in an + * isolated manner. + * + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface SideEffect { + + /** + * Plain text reason for adding this annotation. + */ + String value(); + +} diff --git a/dalvik/src/main/java/dalvik/annotation/Signature.java b/dalvik/src/main/java/dalvik/annotation/Signature.java new file mode 100644 index 0000000..b96d7dd --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/Signature.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide the Signature attribute. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface Signature {} + diff --git a/dalvik/src/main/java/dalvik/annotation/TestLevel.java b/dalvik/src/main/java/dalvik/annotation/TestLevel.java new file mode 100644 index 0000000..f62ea2f --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/TestLevel.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +/** + * Defines an enumeration of possible states a test case can be in. + * + * @hide + */ +public enum TestLevel { + + /** + * Indicates that a test method completely tests its target API method. + */ + COMPLETE, + + /** + * Indicates that a test method partially tests its target API method and + * that together with all other {@code PARTIAL_COMPLETE} tests for the same + * method it is sufficient. + */ + PARTIAL_COMPLETE, + + /** + * Just for compatibility purposes, will be removed later. + */ + PARTIAL_OK, + + /** + * Indicates that a test method partially tests its target API method. It + * needs a second review phase to find out if the sum of all partial tests + * is sufficient for completely testing the target API method. If yes, the + * level has to be changed to {@code PARTIAL_COMPLETE}. + */ + PARTIAL, + + /** + * Indicates that a test method is known to not completely test an API + * method but the missing test steps are too complex and costly to + * implement. This level is positioned somewhere between {@code PARTIAL} + * and {@code COMPLETE}. + */ + SUFFICIENT, + + /** + * Indicates that a test method provides additional testing for an API + * method for which there already exists one {@code COMPLETE} or a set of + * {@code PARTIAL_COMPLETE} tests. This level may also be used for test + * methods that test a concept which can not be directly attributed to + * the specification of an API method or class. + */ + ADDITIONAL, + + /** + * Indicates that there is nothing to test in an API method, for example if + * the specification states that a method does nothing. + */ + NOT_NECESSARY, + + /** + * Indicates that it is very hard or impossible to test an API method. + */ + NOT_FEASIBLE, + + /** + * Indicates that the tests is either insufficient or wrong. Something needs + * to be done about it. + */ + TODO, + +} diff --git a/dalvik/src/main/java/dalvik/annotation/TestTarget.java b/dalvik/src/main/java/dalvik/annotation/TestTarget.java new file mode 100644 index 0000000..dcfd20c --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/TestTarget.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines an annotation used be used within the TestInfo annotation. It + * specifies a single method target for the test (but can be used multiple + * times). + * + * @deprecated Obsolete + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +@Deprecated +public @interface TestTarget { + + /** + * Specifies the name of the method that is being tested. + */ + String methodName() default ""; + + /** + * Specifies the name of a concept being tested. Use this if + * {@code methodName} is not accurate enough. E.g. for + * {@link java.util.regex.Pattern#compile(String)} {@code methodName} is not + * sufficient since the String contains a pattern with its own syntax which + * has to be tested with different aspects. Areas concerned are e.g. JDBC + * (SELECT, INSERT, UPDATE, DELETE, ...), regex (character sets, + * operators,...), formatters (DecimalFormat, DateFormat, ChoiceFormat, + * ...), ... + */ + String conceptName() default ""; + + /** + * Specifies the signature of the method that is being tested, in terms of + * Java classes. + */ + Class<?>[] methodArgs() default {}; + +} diff --git a/dalvik/src/main/java/dalvik/annotation/TestTargetClass.java b/dalvik/src/main/java/dalvik/annotation/TestTargetClass.java new file mode 100644 index 0000000..e88040e --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/TestTargetClass.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines an annotation for test classes that allows to link them to the class + * that is being tested. The current assumption is that the test are somewhat + * organized according to the API classes they test. Might be too strict for + * some cases. + * + * @deprecated Obsolete + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +@Deprecated +public @interface TestTargetClass { + + /** + * Specifies the class being tested. + */ + Class<?> value(); + + /** + * Option to specify untested methods for the class. + * @hide + */ + TestTargetNew[] untestedMethods() default {}; +} diff --git a/dalvik/src/main/java/dalvik/annotation/TestTargetNew.java b/dalvik/src/main/java/dalvik/annotation/TestTargetNew.java new file mode 100644 index 0000000..80aebee --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/TestTargetNew.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines an annotation used be used within the TestInfo annotation. It + * specifies a single method target for the test (but can be used multiple + * times). + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface TestTargetNew { + + /** + * Specifies the name of the API method that is being tested. This field + * may be left empty if the test target is a concept implemented in a + * class rather than a concrete API method. + */ + String method() default ""; + + /** + * Specifies the signature of the API method that is being tested, in terms + * of Java classes. + */ + Class<?>[] args() default {}; + + /** + * Specifies the class to which the tested method belongs. If this value is + * not provided, the class identified in @TestTargetClass is used by the + * test progress doclet. + */ + Class<?> clazz() default void.class; + + /** + * Specifies the level of coverage the tested API method has. + */ + TestLevel level(); + + /** + * Specifies noteworthy plain-text information about the test, for example + * if something is NOT covered by the test method. + */ + String notes() default ""; +} diff --git a/dalvik/src/main/java/dalvik/annotation/TestTargets.java b/dalvik/src/main/java/dalvik/annotation/TestTargets.java new file mode 100644 index 0000000..207572c --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/TestTargets.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines an annotation for test classes that allows to link them to the class + * that is being tested. The current assumption is that the test are somewhat + * organized according to the API classes they test. Might be too strict for + * some cases. + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface TestTargets { + + /** + * Specifies the API methods that are tested by the annotated test method. + */ + TestTargetNew[] value(); +} diff --git a/dalvik/src/main/java/dalvik/annotation/Throws.java b/dalvik/src/main/java/dalvik/annotation/Throws.java new file mode 100644 index 0000000..161116a --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/Throws.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A "system annotation" used to provide the Exceptions attribute. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface Throws {} + diff --git a/dalvik/src/main/java/dalvik/annotation/ToBeFixed.java b/dalvik/src/main/java/dalvik/annotation/ToBeFixed.java new file mode 100644 index 0000000..751a038 --- /dev/null +++ b/dalvik/src/main/java/dalvik/annotation/ToBeFixed.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008 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 dalvik.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines an annotation for test methods that indicate the test method + * need to be fixed in future. + * {@hide pending API Council approval} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface ToBeFixed { + + /** + * Specifies the related bug number on CTS bug tracking system. + */ + String bug() default ""; + + /** + * Specifies why this method need to be fixed. If we think it's a bug, explain + * the expectation and the actual result. + */ + String explanation() default ""; +} diff --git a/dalvik/src/main/java/dalvik/bytecode/OpcodeInfo.java b/dalvik/src/main/java/dalvik/bytecode/OpcodeInfo.java new file mode 100644 index 0000000..1209b2e --- /dev/null +++ b/dalvik/src/main/java/dalvik/bytecode/OpcodeInfo.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 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 dalvik.bytecode; + +/** + * Information about Dalvik opcodes. + */ +public final class OpcodeInfo { + /** + * The maximum possible value of a Dalvik opcode. + * + * <p><b>Note:</b>: This is constant in any given VM incarnation, + * but it is subject to change over time, so it is not appropriate + * to represent as a compile-time constant value.</p> + */ + public static final int MAXIMUM_VALUE; + + /** + * The maximum possible "packed value" of a Dalvik opcode. The + * packed value of an opcode is a denser representation that is + * only used when reporting usage statistics. The mapping between + * packed opcode values and regular opcode values is + * implementation-specific and may vary over time. + * + * <p><b>Note:</b>: This is constant in any given VM incarnation, + * but it is subject to change over time, so it is not appropriate + * to represent as a compile-time constant value.</p> + * + * @see dalvik.system.VMDebug.getInstructionCount() + */ + public static final int MAXIMUM_PACKED_VALUE; + + static { + /* + * See note on the definition of MAXIMUM_VALUE, above, for + * why it's not assigned directly on the declaration line. + * + * IMPORTANT NOTE: This assignment is generated automatically + * by the opcode-gen tool. Any edits will get wiped out the + * next time the tool is run. + */ + // BEGIN(libcore-maximum-values); GENERATED AUTOMATICALLY BY opcode-gen + MAXIMUM_VALUE = 65535; + MAXIMUM_PACKED_VALUE = 255; + // END(libcore-maximum-values) + } + + /** + * This class is not instantiable. + */ + private OpcodeInfo() { + // This space intentionally left blank. + } + + /** + * Returns whether the given packed opcode value represents a + * method invocation operation. This includes most things that + * look like method invocation at the source level, but it notably + * excludes methods that are implemented directly in the VM as + * well as ones the VM knows to have empty implementations. + * + * @hide Unclear if this is useful enough to publish as supported API. + * + * @param opcode one of the values defined in {@link Opcodes} + */ + public static native boolean isInvoke(int packedOpcode); +} diff --git a/dalvik/src/main/java/dalvik/bytecode/Opcodes.java b/dalvik/src/main/java/dalvik/bytecode/Opcodes.java new file mode 100644 index 0000000..f758d65 --- /dev/null +++ b/dalvik/src/main/java/dalvik/bytecode/Opcodes.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2007 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 dalvik.bytecode; + +/** + * A list of all normal (not implementation-specific) Dalvik opcodes. + */ +public interface Opcodes { + /* + * IMPORTANT NOTE: The contents of this file are mostly generated + * automatically by the opcode-gen tool. Any edits to the generated + * sections will get wiped out the next time the tool is run. + */ + + // BEGIN(libcore-opcodes); GENERATED AUTOMATICALLY BY opcode-gen + int OP_NOP = 0x0000; + int OP_MOVE = 0x0001; + int OP_MOVE_FROM16 = 0x0002; + int OP_MOVE_16 = 0x0003; + int OP_MOVE_WIDE = 0x0004; + int OP_MOVE_WIDE_FROM16 = 0x0005; + int OP_MOVE_WIDE_16 = 0x0006; + int OP_MOVE_OBJECT = 0x0007; + int OP_MOVE_OBJECT_FROM16 = 0x0008; + int OP_MOVE_OBJECT_16 = 0x0009; + int OP_MOVE_RESULT = 0x000a; + int OP_MOVE_RESULT_WIDE = 0x000b; + int OP_MOVE_RESULT_OBJECT = 0x000c; + int OP_MOVE_EXCEPTION = 0x000d; + int OP_RETURN_VOID = 0x000e; + int OP_RETURN = 0x000f; + int OP_RETURN_WIDE = 0x0010; + int OP_RETURN_OBJECT = 0x0011; + int OP_CONST_4 = 0x0012; + int OP_CONST_16 = 0x0013; + int OP_CONST = 0x0014; + int OP_CONST_HIGH16 = 0x0015; + int OP_CONST_WIDE_16 = 0x0016; + int OP_CONST_WIDE_32 = 0x0017; + int OP_CONST_WIDE = 0x0018; + int OP_CONST_WIDE_HIGH16 = 0x0019; + int OP_CONST_STRING = 0x001a; + int OP_CONST_STRING_JUMBO = 0x001b; + int OP_CONST_CLASS = 0x001c; + int OP_MONITOR_ENTER = 0x001d; + int OP_MONITOR_EXIT = 0x001e; + int OP_CHECK_CAST = 0x001f; + int OP_INSTANCE_OF = 0x0020; + int OP_ARRAY_LENGTH = 0x0021; + int OP_NEW_INSTANCE = 0x0022; + int OP_NEW_ARRAY = 0x0023; + int OP_FILLED_NEW_ARRAY = 0x0024; + int OP_FILLED_NEW_ARRAY_RANGE = 0x0025; + int OP_FILL_ARRAY_DATA = 0x0026; + int OP_THROW = 0x0027; + int OP_GOTO = 0x0028; + int OP_GOTO_16 = 0x0029; + int OP_GOTO_32 = 0x002a; + int OP_PACKED_SWITCH = 0x002b; + int OP_SPARSE_SWITCH = 0x002c; + int OP_CMPL_FLOAT = 0x002d; + int OP_CMPG_FLOAT = 0x002e; + int OP_CMPL_DOUBLE = 0x002f; + int OP_CMPG_DOUBLE = 0x0030; + int OP_CMP_LONG = 0x0031; + int OP_IF_EQ = 0x0032; + int OP_IF_NE = 0x0033; + int OP_IF_LT = 0x0034; + int OP_IF_GE = 0x0035; + int OP_IF_GT = 0x0036; + int OP_IF_LE = 0x0037; + int OP_IF_EQZ = 0x0038; + int OP_IF_NEZ = 0x0039; + int OP_IF_LTZ = 0x003a; + int OP_IF_GEZ = 0x003b; + int OP_IF_GTZ = 0x003c; + int OP_IF_LEZ = 0x003d; + int OP_AGET = 0x0044; + int OP_AGET_WIDE = 0x0045; + int OP_AGET_OBJECT = 0x0046; + int OP_AGET_BOOLEAN = 0x0047; + int OP_AGET_BYTE = 0x0048; + int OP_AGET_CHAR = 0x0049; + int OP_AGET_SHORT = 0x004a; + int OP_APUT = 0x004b; + int OP_APUT_WIDE = 0x004c; + int OP_APUT_OBJECT = 0x004d; + int OP_APUT_BOOLEAN = 0x004e; + int OP_APUT_BYTE = 0x004f; + int OP_APUT_CHAR = 0x0050; + int OP_APUT_SHORT = 0x0051; + int OP_IGET = 0x0052; + int OP_IGET_WIDE = 0x0053; + int OP_IGET_OBJECT = 0x0054; + int OP_IGET_BOOLEAN = 0x0055; + int OP_IGET_BYTE = 0x0056; + int OP_IGET_CHAR = 0x0057; + int OP_IGET_SHORT = 0x0058; + int OP_IPUT = 0x0059; + int OP_IPUT_WIDE = 0x005a; + int OP_IPUT_OBJECT = 0x005b; + int OP_IPUT_BOOLEAN = 0x005c; + int OP_IPUT_BYTE = 0x005d; + int OP_IPUT_CHAR = 0x005e; + int OP_IPUT_SHORT = 0x005f; + int OP_SGET = 0x0060; + int OP_SGET_WIDE = 0x0061; + int OP_SGET_OBJECT = 0x0062; + int OP_SGET_BOOLEAN = 0x0063; + int OP_SGET_BYTE = 0x0064; + int OP_SGET_CHAR = 0x0065; + int OP_SGET_SHORT = 0x0066; + int OP_SPUT = 0x0067; + int OP_SPUT_WIDE = 0x0068; + int OP_SPUT_OBJECT = 0x0069; + int OP_SPUT_BOOLEAN = 0x006a; + int OP_SPUT_BYTE = 0x006b; + int OP_SPUT_CHAR = 0x006c; + int OP_SPUT_SHORT = 0x006d; + int OP_INVOKE_VIRTUAL = 0x006e; + int OP_INVOKE_SUPER = 0x006f; + int OP_INVOKE_DIRECT = 0x0070; + int OP_INVOKE_STATIC = 0x0071; + int OP_INVOKE_INTERFACE = 0x0072; + int OP_INVOKE_VIRTUAL_RANGE = 0x0074; + int OP_INVOKE_SUPER_RANGE = 0x0075; + int OP_INVOKE_DIRECT_RANGE = 0x0076; + int OP_INVOKE_STATIC_RANGE = 0x0077; + int OP_INVOKE_INTERFACE_RANGE = 0x0078; + int OP_NEG_INT = 0x007b; + int OP_NOT_INT = 0x007c; + int OP_NEG_LONG = 0x007d; + int OP_NOT_LONG = 0x007e; + int OP_NEG_FLOAT = 0x007f; + int OP_NEG_DOUBLE = 0x0080; + int OP_INT_TO_LONG = 0x0081; + int OP_INT_TO_FLOAT = 0x0082; + int OP_INT_TO_DOUBLE = 0x0083; + int OP_LONG_TO_INT = 0x0084; + int OP_LONG_TO_FLOAT = 0x0085; + int OP_LONG_TO_DOUBLE = 0x0086; + int OP_FLOAT_TO_INT = 0x0087; + int OP_FLOAT_TO_LONG = 0x0088; + int OP_FLOAT_TO_DOUBLE = 0x0089; + int OP_DOUBLE_TO_INT = 0x008a; + int OP_DOUBLE_TO_LONG = 0x008b; + int OP_DOUBLE_TO_FLOAT = 0x008c; + int OP_INT_TO_BYTE = 0x008d; + int OP_INT_TO_CHAR = 0x008e; + int OP_INT_TO_SHORT = 0x008f; + int OP_ADD_INT = 0x0090; + int OP_SUB_INT = 0x0091; + int OP_MUL_INT = 0x0092; + int OP_DIV_INT = 0x0093; + int OP_REM_INT = 0x0094; + int OP_AND_INT = 0x0095; + int OP_OR_INT = 0x0096; + int OP_XOR_INT = 0x0097; + int OP_SHL_INT = 0x0098; + int OP_SHR_INT = 0x0099; + int OP_USHR_INT = 0x009a; + int OP_ADD_LONG = 0x009b; + int OP_SUB_LONG = 0x009c; + int OP_MUL_LONG = 0x009d; + int OP_DIV_LONG = 0x009e; + int OP_REM_LONG = 0x009f; + int OP_AND_LONG = 0x00a0; + int OP_OR_LONG = 0x00a1; + int OP_XOR_LONG = 0x00a2; + int OP_SHL_LONG = 0x00a3; + int OP_SHR_LONG = 0x00a4; + int OP_USHR_LONG = 0x00a5; + int OP_ADD_FLOAT = 0x00a6; + int OP_SUB_FLOAT = 0x00a7; + int OP_MUL_FLOAT = 0x00a8; + int OP_DIV_FLOAT = 0x00a9; + int OP_REM_FLOAT = 0x00aa; + int OP_ADD_DOUBLE = 0x00ab; + int OP_SUB_DOUBLE = 0x00ac; + int OP_MUL_DOUBLE = 0x00ad; + int OP_DIV_DOUBLE = 0x00ae; + int OP_REM_DOUBLE = 0x00af; + int OP_ADD_INT_2ADDR = 0x00b0; + int OP_SUB_INT_2ADDR = 0x00b1; + int OP_MUL_INT_2ADDR = 0x00b2; + int OP_DIV_INT_2ADDR = 0x00b3; + int OP_REM_INT_2ADDR = 0x00b4; + int OP_AND_INT_2ADDR = 0x00b5; + int OP_OR_INT_2ADDR = 0x00b6; + int OP_XOR_INT_2ADDR = 0x00b7; + int OP_SHL_INT_2ADDR = 0x00b8; + int OP_SHR_INT_2ADDR = 0x00b9; + int OP_USHR_INT_2ADDR = 0x00ba; + int OP_ADD_LONG_2ADDR = 0x00bb; + int OP_SUB_LONG_2ADDR = 0x00bc; + int OP_MUL_LONG_2ADDR = 0x00bd; + int OP_DIV_LONG_2ADDR = 0x00be; + int OP_REM_LONG_2ADDR = 0x00bf; + int OP_AND_LONG_2ADDR = 0x00c0; + int OP_OR_LONG_2ADDR = 0x00c1; + int OP_XOR_LONG_2ADDR = 0x00c2; + int OP_SHL_LONG_2ADDR = 0x00c3; + int OP_SHR_LONG_2ADDR = 0x00c4; + int OP_USHR_LONG_2ADDR = 0x00c5; + int OP_ADD_FLOAT_2ADDR = 0x00c6; + int OP_SUB_FLOAT_2ADDR = 0x00c7; + int OP_MUL_FLOAT_2ADDR = 0x00c8; + int OP_DIV_FLOAT_2ADDR = 0x00c9; + int OP_REM_FLOAT_2ADDR = 0x00ca; + int OP_ADD_DOUBLE_2ADDR = 0x00cb; + int OP_SUB_DOUBLE_2ADDR = 0x00cc; + int OP_MUL_DOUBLE_2ADDR = 0x00cd; + int OP_DIV_DOUBLE_2ADDR = 0x00ce; + int OP_REM_DOUBLE_2ADDR = 0x00cf; + int OP_ADD_INT_LIT16 = 0x00d0; + int OP_RSUB_INT = 0x00d1; + int OP_MUL_INT_LIT16 = 0x00d2; + int OP_DIV_INT_LIT16 = 0x00d3; + int OP_REM_INT_LIT16 = 0x00d4; + int OP_AND_INT_LIT16 = 0x00d5; + int OP_OR_INT_LIT16 = 0x00d6; + int OP_XOR_INT_LIT16 = 0x00d7; + int OP_ADD_INT_LIT8 = 0x00d8; + int OP_RSUB_INT_LIT8 = 0x00d9; + int OP_MUL_INT_LIT8 = 0x00da; + int OP_DIV_INT_LIT8 = 0x00db; + int OP_REM_INT_LIT8 = 0x00dc; + int OP_AND_INT_LIT8 = 0x00dd; + int OP_OR_INT_LIT8 = 0x00de; + int OP_XOR_INT_LIT8 = 0x00df; + int OP_SHL_INT_LIT8 = 0x00e0; + int OP_SHR_INT_LIT8 = 0x00e1; + int OP_USHR_INT_LIT8 = 0x00e2; + // END(libcore-opcodes) + + /** Never implemented; do not use. */ + int OP_CONST_CLASS_JUMBO = 0x00ff; + /** Never implemented; do not use. */ + int OP_CHECK_CAST_JUMBO = 0x01ff; + /** Never implemented; do not use. */ + int OP_INSTANCE_OF_JUMBO = 0x02ff; + /** Never implemented; do not use. */ + int OP_NEW_INSTANCE_JUMBO = 0x03ff; + /** Never implemented; do not use. */ + int OP_NEW_ARRAY_JUMBO = 0x04ff; + /** Never implemented; do not use. */ + int OP_FILLED_NEW_ARRAY_JUMBO = 0x05ff; + /** Never implemented; do not use. */ + int OP_IGET_JUMBO = 0x06ff; + /** Never implemented; do not use. */ + int OP_IGET_WIDE_JUMBO = 0x07ff; + /** Never implemented; do not use. */ + int OP_IGET_OBJECT_JUMBO = 0x08ff; + /** Never implemented; do not use. */ + int OP_IGET_BOOLEAN_JUMBO = 0x09ff; + /** Never implemented; do not use. */ + int OP_IGET_BYTE_JUMBO = 0x0aff; + /** Never implemented; do not use. */ + int OP_IGET_CHAR_JUMBO = 0x0bff; + /** Never implemented; do not use. */ + int OP_IGET_SHORT_JUMBO = 0x0cff; + /** Never implemented; do not use. */ + int OP_IPUT_JUMBO = 0x0dff; + /** Never implemented; do not use. */ + int OP_IPUT_WIDE_JUMBO = 0x0eff; + /** Never implemented; do not use. */ + int OP_IPUT_OBJECT_JUMBO = 0x0fff; + /** Never implemented; do not use. */ + int OP_IPUT_BOOLEAN_JUMBO = 0x10ff; + /** Never implemented; do not use. */ + int OP_IPUT_BYTE_JUMBO = 0x11ff; + /** Never implemented; do not use. */ + int OP_IPUT_CHAR_JUMBO = 0x12ff; + /** Never implemented; do not use. */ + int OP_IPUT_SHORT_JUMBO = 0x13ff; + /** Never implemented; do not use. */ + int OP_SGET_JUMBO = 0x14ff; + /** Never implemented; do not use. */ + int OP_SGET_WIDE_JUMBO = 0x15ff; + /** Never implemented; do not use. */ + int OP_SGET_OBJECT_JUMBO = 0x16ff; + /** Never implemented; do not use. */ + int OP_SGET_BOOLEAN_JUMBO = 0x17ff; + /** Never implemented; do not use. */ + int OP_SGET_BYTE_JUMBO = 0x18ff; + /** Never implemented; do not use. */ + int OP_SGET_CHAR_JUMBO = 0x19ff; + /** Never implemented; do not use. */ + int OP_SGET_SHORT_JUMBO = 0x1aff; + /** Never implemented; do not use. */ + int OP_SPUT_JUMBO = 0x1bff; + /** Never implemented; do not use. */ + int OP_SPUT_WIDE_JUMBO = 0x1cff; + /** Never implemented; do not use. */ + int OP_SPUT_OBJECT_JUMBO = 0x1dff; + /** Never implemented; do not use. */ + int OP_SPUT_BOOLEAN_JUMBO = 0x1eff; + /** Never implemented; do not use. */ + int OP_SPUT_BYTE_JUMBO = 0x1fff; + /** Never implemented; do not use. */ + int OP_SPUT_CHAR_JUMBO = 0x20ff; + /** Never implemented; do not use. */ + int OP_SPUT_SHORT_JUMBO = 0x21ff; + /** Never implemented; do not use. */ + int OP_INVOKE_VIRTUAL_JUMBO = 0x22ff; + /** Never implemented; do not use. */ + int OP_INVOKE_SUPER_JUMBO = 0x23ff; + /** Never implemented; do not use. */ + int OP_INVOKE_DIRECT_JUMBO = 0x24ff; + /** Never implemented; do not use. */ + int OP_INVOKE_STATIC_JUMBO = 0x25ff; + /** Never implemented; do not use. */ + int OP_INVOKE_INTERFACE_JUMBO = 0x26ff; + + /* + * The rest of these are either generated by dexopt for optimized + * code, or inserted by the VM at runtime. They are never generated + * by "dx". + * + * They are all deprecated and will be removed in a future + * release, since these declarations are really of private implementation + * details that are subject to change. + */ + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IGET_WIDE_VOLATILE = 0xe8; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IPUT_WIDE_VOLATILE = 0xe9; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_SGET_WIDE_VOLATILE = 0xea; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_SPUT_WIDE_VOLATILE = 0xeb; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_BREAKPOINT = 0xec; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_THROW_VERIFICATION_ERROR = 0xed; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_EXECUTE_INLINE = 0xee; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_EXECUTE_INLINE_RANGE = 0xef; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_INVOKE_DIRECT_EMPTY = 0xf0; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IGET_QUICK = 0xf2; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IGET_WIDE_QUICK = 0xf3; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IGET_OBJECT_QUICK = 0xf4; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IPUT_QUICK = 0xf5; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IPUT_WIDE_QUICK = 0xf6; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_IPUT_OBJECT_QUICK = 0xf7; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_INVOKE_VIRTUAL_QUICK = 0xf8; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_INVOKE_VIRTUAL_QUICK_RANGE = 0xf9; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_INVOKE_SUPER_QUICK = 0xfa; + + /** + * Implementation detail. + * @deprecated Implementation detail. + */ + @Deprecated int OP_INVOKE_SUPER_QUICK_RANGE = 0xfb; +} diff --git a/dalvik/src/main/java/dalvik/system/AllocationLimitError.java b/dalvik/src/main/java/dalvik/system/AllocationLimitError.java new file mode 100644 index 0000000..b3f8947 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/AllocationLimitError.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 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 dalvik.system; + +/** + * Is thrown when an allocation limit is exceeded. + * + * @hide + */ +public class AllocationLimitError extends VirtualMachineError { + /** + * Creates a new exception instance and initializes it with default values. + */ + public AllocationLimitError() { + super(); + } + + /** + * Creates a new exception instance and initializes it with a given message. + * + * @param detailMessage the error message + */ + public AllocationLimitError(String detailMessage) { + super(detailMessage); + } +} + diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java new file mode 100644 index 0000000..d3ec95a --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 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 dalvik.system; + +import java.io.File; +import java.net.URL; +import java.util.Enumeration; + +/** + * Base class for common functionality between various dex-based + * {@link ClassLoader} implementations. + */ +public class BaseDexClassLoader extends ClassLoader { + private final DexPathList pathList; + + /** + * Constructs an instance. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param optimizedDirectory directory where optimized dex files + * should be written; may be {@code null} + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public BaseDexClassLoader(String dexPath, File optimizedDirectory, + String libraryPath, ClassLoader parent) { + super(parent); + this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + Class c = pathList.findClass(name); + if (c == null) { + throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); + } + return c; + } + + @Override + protected URL findResource(String name) { + return pathList.findResource(name); + } + + @Override + protected Enumeration<URL> findResources(String name) { + return pathList.findResources(name); + } + + @Override + public String findLibrary(String name) { + return pathList.findLibrary(name); + } + + /** + * Returns package information for the given package. + * Unfortunately, instances of this class don't really have this + * information, and as a non-secure {@code ClassLoader}, it isn't + * even required to, according to the spec. Yet, we want to + * provide it, in order to make all those hopeful callers of + * {@code myClass.getPackage().getName()} happy. Thus we construct + * a {@code Package} object the first time it is being requested + * and fill most of the fields with dummy values. The {@code + * Package} object is then put into the {@code ClassLoader}'s + * package cache, so we see the same one next time. We don't + * create {@code Package} objects for {@code null} arguments or + * for the default package. + * + * <p>There is a limited chance that we end up with multiple + * {@code Package} objects representing the same package: It can + * happen when when a package is scattered across different JAR + * files which were loaded by different {@code ClassLoader} + * instances. This is rather unlikely, and given that this whole + * thing is more or less a workaround, probably not worth the + * effort to address. + * + * @param name the name of the class + * @return the package information for the class, or {@code null} + * if there is no package information available for it + */ + @Override + protected synchronized Package getPackage(String name) { + if (name != null && !name.isEmpty()) { + Package pack = super.getPackage(name); + + if (pack == null) { + pack = definePackage(name, "Unknown", "0.0", "Unknown", + "Unknown", "0.0", "Unknown", null); + } + + return pack; + } + + return null; + } + + /** + * @hide + */ + public String getLdLibraryPath() { + StringBuilder result = new StringBuilder(); + for (File directory : pathList.getNativeLibraryDirectories()) { + if (result.length() > 0) { + result.append(':'); + } + result.append(directory); + } + return result.toString(); + } + + @Override public String toString() { + return getClass().getName() + "[" + pathList + "]"; + } +} diff --git a/dalvik/src/main/java/dalvik/system/BlockGuard.java b/dalvik/src/main/java/dalvik/system/BlockGuard.java new file mode 100644 index 0000000..d61f0e1 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/BlockGuard.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2010 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 dalvik.system; + +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.SocketException; +import java.nio.charset.Charsets; + +/** + * Mechanism to let threads set restrictions on what code is allowed + * to do in their thread. + * + * <p>This is meant for applications to prevent certain blocking + * operations from running on their main event loop (or "UI") threads. + * + * <p>Note that this is all best-effort to catch most accidental mistakes + * and isn't intended to be a perfect mechanism, nor provide any sort of + * security. + * + * @hide + */ +public final class BlockGuard { + + // TODO: refactor class name to something more generic, since its scope is + // growing beyond just blocking/logging. + + public static final int DISALLOW_DISK_WRITE = 0x01; + public static final int DISALLOW_DISK_READ = 0x02; + public static final int DISALLOW_NETWORK = 0x04; + public static final int PASS_RESTRICTIONS_VIA_RPC = 0x08; + public static final int PENALTY_LOG = 0x10; + public static final int PENALTY_DIALOG = 0x20; + public static final int PENALTY_DEATH = 0x40; + + public interface Policy { + /** + * Called on disk writes. + */ + void onWriteToDisk(); + + /** + * Called on disk reads. + */ + void onReadFromDisk(); + + /** + * Called on network operations. + */ + void onNetwork(); + + /** + * Returns the policy bitmask, for shipping over Binder calls + * to remote threads/processes and reinstantiating the policy + * there. The bits in the mask are from the DISALLOW_* and + * PENALTY_* constants. + */ + int getPolicyMask(); + } + + public static class BlockGuardPolicyException extends RuntimeException { + // bitmask of DISALLOW_*, PENALTY_*, etc flags + private final int mPolicyState; + private final int mPolicyViolated; + private final String mMessage; // may be null + + public BlockGuardPolicyException(int policyState, int policyViolated) { + this(policyState, policyViolated, null); + } + + public BlockGuardPolicyException(int policyState, int policyViolated, String message) { + mPolicyState = policyState; + mPolicyViolated = policyViolated; + mMessage = message; + fillInStackTrace(); + } + + public int getPolicy() { + return mPolicyState; + } + + public int getPolicyViolation() { + return mPolicyViolated; + } + + public String getMessage() { + // Note: do not change this format casually. It's + // somewhat unfortunately Parceled and passed around + // Binder calls and parsed back into an Exception by + // Android's StrictMode. This was the least invasive + // option and avoided a gross mix of Java Serialization + // combined with Parcels. + return "policy=" + mPolicyState + " violation=" + mPolicyViolated + + (mMessage == null ? "" : (" msg=" + mMessage)); + } + } + + /** + * The default, permissive policy that doesn't prevent any operations. + */ + public static final Policy LAX_POLICY = new Policy() { + public void onWriteToDisk() {} + public void onReadFromDisk() {} + public void onNetwork() {} + public int getPolicyMask() { + return 0; + } + }; + + private static ThreadLocal<Policy> threadPolicy = new ThreadLocal<Policy>() { + @Override protected Policy initialValue() { + return LAX_POLICY; + } + }; + + /** + * Get the current thread's policy. + * + * @return the current thread's policy. Never returns null. + * Will return the LAX_POLICY instance if nothing else is set. + */ + public static Policy getThreadPolicy() { + return threadPolicy.get(); + } + + /** + * Sets the current thread's block guard policy. + * + * @param policy policy to set. May not be null. Use the public LAX_POLICY + * if you want to unset the active policy. + */ + public static void setThreadPolicy(Policy policy) { + if (policy == null) { + throw new NullPointerException("policy == null"); + } + threadPolicy.set(policy); + } + + private BlockGuard() {} +} diff --git a/dalvik/src/main/java/dalvik/system/CloseGuard.java b/dalvik/src/main/java/dalvik/system/CloseGuard.java new file mode 100644 index 0000000..df36867 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/CloseGuard.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2010 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 dalvik.system; + +/** + * CloseGuard is a mechanism for flagging implicit finalizer cleanup of + * resources that should have been cleaned up by explicit close + * methods (aka "explicit termination methods" in Effective Java). + * <p> + * A simple example: <pre> {@code + * class Foo { + * + * private final CloseGuard guard = CloseGuard.get(); + * + * ... + * + * public Foo() { + * ...; + * guard.open("cleanup"); + * } + * + * public void cleanup() { + * guard.close(); + * ...; + * } + * + * protected void finalize() throws Throwable { + * try { + * if (guard != null) { + * guard.warnIfOpen(); + * } + * cleanup(); + * } finally { + * super.finalize(); + * } + * } + * } + * }</pre> + * + * In usage where the resource to be explicitly cleaned up are + * allocated after object construction, CloseGuard protection can + * be deferred. For example: <pre> {@code + * class Bar { + * + * private final CloseGuard guard = CloseGuard.get(); + * + * ... + * + * public Bar() { + * ...; + * } + * + * public void connect() { + * ...; + * guard.open("cleanup"); + * } + * + * public void cleanup() { + * guard.close(); + * ...; + * } + * + * protected void finalize() throws Throwable { + * try { + * if (guard != null) { + * guard.warnIfOpen(); + * } + * cleanup(); + * } finally { + * super.finalize(); + * } + * } + * } + * }</pre> + * + * When used in a constructor calls to {@code open} should occur at + * the end of the constructor since an exception that would cause + * abrupt termination of the constructor will mean that the user will + * not have a reference to the object to cleanup explicitly. When used + * in a method, the call to {@code open} should occur just after + * resource acquisition. + * + * <p> + * + * Note that the null check on {@code guard} in the finalizer is to + * cover cases where a constructor throws an exception causing the + * {@code guard} to be uninitialized. + * + * @hide + */ +public final class CloseGuard { + + /** + * Instance used when CloseGuard is disabled to avoid allocation. + */ + private static final CloseGuard NOOP = new CloseGuard(); + + /** + * Enabled by default so we can catch issues early in VM startup. + * Note, however, that Android disables this early in its startup, + * but enables it with DropBoxing for system apps on debug builds. + */ + private static volatile boolean ENABLED = true; + + /** + * Hook for customizing how CloseGuard issues are reported. + */ + private static volatile Reporter REPORTER = new DefaultReporter(); + + /** + * Returns a CloseGuard instance. If CloseGuard is enabled, {@code + * #open(String)} can be used to set up the instance to warn on + * failure to close. If CloseGuard is disabled, a non-null no-op + * instance is returned. + */ + public static CloseGuard get() { + if (!ENABLED) { + return NOOP; + } + return new CloseGuard(); + } + + /** + * Used to enable or disable CloseGuard. Note that CloseGuard only + * warns if it is enabled for both allocation and finalization. + */ + public static void setEnabled(boolean enabled) { + ENABLED = enabled; + } + + /** + * Used to replace default Reporter used to warn of CloseGuard + * violations. Must be non-null. + */ + public static void setReporter(Reporter reporter) { + if (reporter == null) { + throw new NullPointerException("reporter == null"); + } + REPORTER = reporter; + } + + /** + * Returns non-null CloseGuard.Reporter. + */ + public static Reporter getReporter() { + return REPORTER; + } + + private CloseGuard() {} + + /** + * If CloseGuard is enabled, {@code open} initializes the instance + * with a warning that the caller should have explicitly called the + * {@code closer} method instead of relying on finalization. + * + * @param closer non-null name of explicit termination method + * @throws NullPointerException if closer is null, regardless of + * whether or not CloseGuard is enabled + */ + public void open(String closer) { + // always perform the check for valid API usage... + if (closer == null) { + throw new NullPointerException("closer == null"); + } + // ...but avoid allocating an allocationSite if disabled + if (this == NOOP || !ENABLED) { + return; + } + String message = "Explicit termination method '" + closer + "' not called"; + allocationSite = new Throwable(message); + } + + private Throwable allocationSite; + + /** + * Marks this CloseGuard instance as closed to avoid warnings on + * finalization. + */ + public void close() { + allocationSite = null; + } + + /** + * If CloseGuard is enabled, logs a warning if the caller did not + * properly cleanup by calling an explicit close method + * before finalization. If CloseGuard is disabled, no action is + * performed. + */ + public void warnIfOpen() { + if (allocationSite == null || !ENABLED) { + return; + } + + String message = + ("A resource was acquired at attached stack trace but never released. " + + "See java.io.Closeable for information on avoiding resource leaks."); + + REPORTER.report(message, allocationSite); + } + + /** + * Interface to allow customization of reporting behavior. + */ + public static interface Reporter { + public void report (String message, Throwable allocationSite); + } + + /** + * Default Reporter which reports CloseGuard violations to the log. + */ + private static final class DefaultReporter implements Reporter { + @Override public void report (String message, Throwable allocationSite) { + System.logW(message, allocationSite); + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/DalvikLogHandler.java b/dalvik/src/main/java/dalvik/system/DalvikLogHandler.java new file mode 100644 index 0000000..a62ca3b --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/DalvikLogHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009 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 dalvik.system; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An optimized handler for efficient publishing of basic log messages. + * Implementers should also be subclasses of {@link java.util.logging.Handler}. + * + * <p>Unlike the default log handler, this API doesn't require intermediate + * objects to be allocated for log handling. It also includes a short tag, which + * may otherwise need to be calculated for each published message. + * + * @hide + */ +public interface DalvikLogHandler { + + /** + * Publishes a log message. Unlike {@link + * java.util.logging.Handler#publish(java.util.logging.LogRecord)}, this + * method includes only the raw log message. Log messages that were created + * with additional fields (parameters, source methods, etc.) will flow + * through the conventional channels instead. + * + * @param tag the short (23 characters or fewer) logger tag identifying + * {@code logger}. + */ + void publish(Logger source, String tag, Level level, String message); + + // TODO: support messages with throwables? +}
\ No newline at end of file diff --git a/dalvik/src/main/java/dalvik/system/DalvikLogging.java b/dalvik/src/main/java/dalvik/system/DalvikLogging.java new file mode 100644 index 0000000..b5c7ad5 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/DalvikLogging.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 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 dalvik.system; + +/** + * Utility methods for logging to {@code DalvikHandlers}. + * + * @hide + */ +public final class DalvikLogging { + private DalvikLogging() {} + + /** + * Returns the short logger tag (up to 23 chars) for the given logger name. + * Traditionally loggers are named by fully-qualified Java classes; this + * method attempts to return a concise identifying part of such names. + */ + public static String loggerNameToTag(String loggerName) { + // Anonymous logger. + if (loggerName == null) { + return "null"; + } + + int length = loggerName.length(); + if (length <= 23) { + return loggerName; + } + + int lastPeriod = loggerName.lastIndexOf("."); + return length - (lastPeriod + 1) <= 23 + ? loggerName.substring(lastPeriod + 1) + : loggerName.substring(loggerName.length() - 23); + } +} diff --git a/dalvik/src/main/java/dalvik/system/DexClassLoader.java b/dalvik/src/main/java/dalvik/system/DexClassLoader.java new file mode 100644 index 0000000..ac2a70a --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/DexClassLoader.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 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 dalvik.system; + +import java.io.File; + +/** + * A class loader that loads classes from {@code .jar} and {@code .apk} files + * containing a {@code classes.dex} entry. This can be used to execute code not + * installed as part of an application. + * + * <p>This class loader requires an application-private, writable directory to + * cache optimized classes. Use {@code Context.getDir(String, int)} to create + * such a directory: <pre> {@code + * File dexOutputDir = context.getDir("dex", 0); + * }</pre> + * + * <p><strong>Do not cache optimized classes on external storage.</strong> + * External storage does not provide access controls necessary to protect your + * application from code injection attacks. + */ +public class DexClassLoader extends BaseDexClassLoader { + /** + * Creates a {@code DexClassLoader} that finds interpreted and native + * code. Interpreted classes are found in a set of DEX files contained + * in Jar or APK files. + * + * <p>The path lists are separated using the character specified by the + * {@code path.separator} system property, which defaults to {@code :}. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param optimizedDirectory directory where optimized dex files + * should be written; must not be {@code null} + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public DexClassLoader(String dexPath, String optimizedDirectory, + String libraryPath, ClassLoader parent) { + super(dexPath, new File(optimizedDirectory), libraryPath, parent); + } +} diff --git a/dalvik/src/main/java/dalvik/system/DexFile.java b/dalvik/src/main/java/dalvik/system/DexFile.java new file mode 100644 index 0000000..8db3985 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/DexFile.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2007 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 dalvik.system; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.StructStat; + +/** + * Manipulates DEX files. The class is similar in principle to + * {@link java.util.zip.ZipFile}. It is used primarily by class loaders. + * <p> + * Note we don't directly open and read the DEX file here. They're memory-mapped + * read-only by the VM. + */ +public final class DexFile { + private int mCookie; + private final String mFileName; + private final CloseGuard guard = CloseGuard.get(); + + /** + * Opens a DEX file from a given File object. This will usually be a ZIP/JAR + * file with a "classes.dex" inside. + * + * The VM will generate the name of the corresponding file in + * /data/dalvik-cache and open it, possibly creating or updating + * it first if system permissions allow. Don't pass in the name of + * a file in /data/dalvik-cache, as the named file is expected to be + * in its original (pre-dexopt) state. + * + * @param file + * the File object referencing the actual DEX file + * + * @throws IOException + * if an I/O error occurs, such as the file not being found or + * access rights missing for opening it + */ + public DexFile(File file) throws IOException { + this(file.getPath()); + } + + /** + * Opens a DEX file from a given filename. This will usually be a ZIP/JAR + * file with a "classes.dex" inside. + * + * The VM will generate the name of the corresponding file in + * /data/dalvik-cache and open it, possibly creating or updating + * it first if system permissions allow. Don't pass in the name of + * a file in /data/dalvik-cache, as the named file is expected to be + * in its original (pre-dexopt) state. + * + * @param fileName + * the filename of the DEX file + * + * @throws IOException + * if an I/O error occurs, such as the file not being found or + * access rights missing for opening it + */ + public DexFile(String fileName) throws IOException { + mCookie = openDexFile(fileName, null, 0); + mFileName = fileName; + guard.open("close"); + //System.out.println("DEX FILE cookie is " + mCookie); + } + + /** + * Opens a DEX file from a given filename, using a specified file + * to hold the optimized data. + * + * @param sourceName + * Jar or APK file with "classes.dex". + * @param outputName + * File that will hold the optimized form of the DEX data. + * @param flags + * Enable optional features. + */ + private DexFile(String sourceName, String outputName, int flags) throws IOException { + if (outputName != null) { + try { + String parent = new File(outputName).getParent(); + if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { + throw new IllegalArgumentException("Optimized data directory " + parent + + " is not owned by the current user. Shared storage cannot protect" + + " your application from code injection attacks."); + } + } catch (ErrnoException ignored) { + // assume we'll fail with a more contextual error later + } + } + + mCookie = openDexFile(sourceName, outputName, flags); + mFileName = sourceName; + guard.open("close"); + //System.out.println("DEX FILE cookie is " + mCookie); + } + + /** + * Open a DEX file, specifying the file in which the optimized DEX + * data should be written. If the optimized form exists and appears + * to be current, it will be used; if not, the VM will attempt to + * regenerate it. + * + * This is intended for use by applications that wish to download + * and execute DEX files outside the usual application installation + * mechanism. This function should not be called directly by an + * application; instead, use a class loader such as + * dalvik.system.DexClassLoader. + * + * @param sourcePathName + * Jar or APK file with "classes.dex". (May expand this to include + * "raw DEX" in the future.) + * @param outputPathName + * File that will hold the optimized form of the DEX data. + * @param flags + * Enable optional features. (Currently none defined.) + * @return + * A new or previously-opened DexFile. + * @throws IOException + * If unable to open the source or output file. + */ + static public DexFile loadDex(String sourcePathName, String outputPathName, + int flags) throws IOException { + + /* + * TODO: we may want to cache previously-opened DexFile objects. + * The cache would be synchronized with close(). This would help + * us avoid mapping the same DEX more than once when an app + * decided to open it multiple times. In practice this may not + * be a real issue. + */ + return new DexFile(sourcePathName, outputPathName, flags); + } + + /** + * Gets the name of the (already opened) DEX file. + * + * @return the file name + */ + public String getName() { + return mFileName; + } + + /** + * Closes the DEX file. + * <p> + * This may not be able to release any resources. If classes from this + * DEX file are still resident, the DEX file can't be unmapped. + * + * @throws IOException + * if an I/O error occurs during closing the file, which + * normally should not happen + */ + public void close() throws IOException { + guard.close(); + closeDexFile(mCookie); + mCookie = 0; + } + + /** + * Loads a class. Returns the class on success, or a {@code null} reference + * on failure. + * <p> + * If you are not calling this from a class loader, this is most likely not + * going to do what you want. Use {@link Class#forName(String)} instead. + * <p> + * The method does not throw {@link ClassNotFoundException} if the class + * isn't found because it isn't reasonable to throw exceptions wildly every + * time a class is not found in the first DEX file we look at. + * + * @param name + * the class name, which should look like "java/lang/String" + * + * @param loader + * the class loader that tries to load the class (in most cases + * the caller of the method + * + * @return the {@link Class} object representing the class, or {@code null} + * if the class cannot be loaded + */ + public Class loadClass(String name, ClassLoader loader) { + String slashName = name.replace('.', '/'); + return loadClassBinaryName(slashName, loader); + } + + /** + * See {@link #loadClass(String, ClassLoader)}. + * + * This takes a "binary" class name to better match ClassLoader semantics. + * + * @hide + */ + public Class loadClassBinaryName(String name, ClassLoader loader) { + return defineClass(name, loader, mCookie); + } + + private native static Class defineClass(String name, ClassLoader loader, int cookie); + + /** + * Enumerate the names of the classes in this DEX file. + * + * @return an enumeration of names of classes contained in the DEX file, in + * the usual internal form (like "java/lang/String"). + */ + public Enumeration<String> entries() { + return new DFEnum(this); + } + + /* + * Helper class. + */ + private class DFEnum implements Enumeration<String> { + private int mIndex; + private String[] mNameList; + + DFEnum(DexFile df) { + mIndex = 0; + mNameList = getClassNameList(mCookie); + } + + public boolean hasMoreElements() { + return (mIndex < mNameList.length); + } + + public String nextElement() { + return mNameList[mIndex++]; + } + } + + /* return a String array with class names */ + native private static String[] getClassNameList(int cookie); + + /** + * Called when the class is finalized. Makes sure the DEX file is closed. + * + * @throws IOException + * if an I/O error occurs during closing the file, which + * normally should not happen + */ + @Override protected void finalize() throws Throwable { + try { + if (guard != null) { + guard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } + } + + /* + * Open a DEX file. The value returned is a magic VM cookie. On + * failure, an IOException is thrown. + */ + native private static int openDexFile(String sourceName, String outputName, + int flags) throws IOException; + + /* + * Open a DEX file based on a {@code byte[]}. The value returned + * is a magic VM cookie. On failure, a RuntimeException is thrown. + */ + native private static int openDexFile(byte[] fileContents); + + /* + * Close DEX file. + */ + native private static void closeDexFile(int cookie); + + /** + * Returns true if the VM believes that the apk/jar file is out of date + * and should be passed through "dexopt" again. + * + * @param fileName the absolute path to the apk/jar file to examine. + * @return true if dexopt should be called on the file, false otherwise. + * @throws java.io.FileNotFoundException if fileName is not readable, + * not a file, or not present. + * @throws java.io.IOException if fileName is not a valid apk/jar file or + * if problems occur while parsing it. + * @throws java.lang.NullPointerException if fileName is null. + * @throws dalvik.system.StaleDexCacheError if the optimized dex file + * is stale but exists on a read-only partition. + */ + native public static boolean isDexOptNeeded(String fileName) + throws FileNotFoundException, IOException; +} diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java new file mode 100644 index 0000000..3d9ee3e --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/DexPathList.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2011 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 dalvik.system; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; +import libcore.io.ErrnoException; +import libcore.io.IoUtils; +import libcore.io.Libcore; +import libcore.io.StructStat; +import static libcore.io.OsConstants.*; + +/** + * A pair of lists of entries, associated with a {@code ClassLoader}. + * One of the lists is a dex/resource path — typically referred + * to as a "class path" — list, and the other names directories + * containing native code libraries. Class path entries may be any of: + * a {@code .jar} or {@code .zip} file containing an optional + * top-level {@code classes.dex} file as well as arbitrary resources, + * or a plain {@code .dex} file (with no possibility of associated + * resources). + * + * <p>This class also contains methods to use these lists to look up + * classes and resources.</p> + */ +/*package*/ final class DexPathList { + private static final String DEX_SUFFIX = ".dex"; + private static final String JAR_SUFFIX = ".jar"; + private static final String ZIP_SUFFIX = ".zip"; + private static final String APK_SUFFIX = ".apk"; + + /** class definition context */ + private final ClassLoader definingContext; + + /** + * List of dex/resource (class path) elements. + * Should be called pathElements, but the Facebook app uses reflection + * to modify 'dexElements' (http://b/7726934). + */ + private final Element[] dexElements; + + /** List of native library directories. */ + private final File[] nativeLibraryDirectories; + + /** + * Constructs an instance. + * + * @param definingContext the context in which any as-yet unresolved + * classes should be defined + * @param dexPath list of dex/resource path elements, separated by + * {@code File.pathSeparator} + * @param libraryPath list of native library directory path elements, + * separated by {@code File.pathSeparator} + * @param optimizedDirectory directory where optimized {@code .dex} files + * should be found and written to, or {@code null} to use the default + * system directory for same + */ + public DexPathList(ClassLoader definingContext, String dexPath, + String libraryPath, File optimizedDirectory) { + if (definingContext == null) { + throw new NullPointerException("definingContext == null"); + } + + if (dexPath == null) { + throw new NullPointerException("dexPath == null"); + } + + if (optimizedDirectory != null) { + if (!optimizedDirectory.exists()) { + throw new IllegalArgumentException( + "optimizedDirectory doesn't exist: " + + optimizedDirectory); + } + + if (!(optimizedDirectory.canRead() + && optimizedDirectory.canWrite())) { + throw new IllegalArgumentException( + "optimizedDirectory not readable/writable: " + + optimizedDirectory); + } + } + + this.definingContext = definingContext; + this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory); + this.nativeLibraryDirectories = splitLibraryPath(libraryPath); + } + + @Override public String toString() { + return "DexPathList[" + Arrays.toString(dexElements) + + ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]"; + } + + /** + * For BaseDexClassLoader.getLdLibraryPath. + */ + public File[] getNativeLibraryDirectories() { + return nativeLibraryDirectories; + } + + /** + * Splits the given dex path string into elements using the path + * separator, pruning out any elements that do not refer to existing + * and readable files. (That is, directories are not included in the + * result.) + */ + private static ArrayList<File> splitDexPath(String path) { + return splitPaths(path, null, false); + } + + /** + * Splits the given library directory path string into elements + * using the path separator ({@code File.pathSeparator}, which + * defaults to {@code ":"} on Android, appending on the elements + * from the system library path, and pruning out any elements that + * do not refer to existing and readable directories. + */ + private static File[] splitLibraryPath(String path) { + /* + * Native libraries may exist in both the system and + * application library paths, and we use this search order: + * + * 1. this class loader's library path for application + * libraries + * 2. the VM's library path from the system + * property for system libraries + * + * This order was reversed prior to Gingerbread; see http://b/2933456. + */ + ArrayList<File> result = splitPaths( + path, System.getProperty("java.library.path", "."), true); + return result.toArray(new File[result.size()]); + } + + /** + * Splits the given path strings into file elements using the path + * separator, combining the results and filtering out elements + * that don't exist, aren't readable, or aren't either a regular + * file or a directory (as specified). Either string may be empty + * or {@code null}, in which case it is ignored. If both strings + * are empty or {@code null}, or all elements get pruned out, then + * this returns a zero-element list. + */ + private static ArrayList<File> splitPaths(String path1, String path2, + boolean wantDirectories) { + ArrayList<File> result = new ArrayList<File>(); + + splitAndAdd(path1, wantDirectories, result); + splitAndAdd(path2, wantDirectories, result); + return result; + } + + /** + * Helper for {@link #splitPaths}, which does the actual splitting + * and filtering and adding to a result. + */ + private static void splitAndAdd(String searchPath, boolean directoriesOnly, + ArrayList<File> resultList) { + if (searchPath == null) { + return; + } + for (String path : searchPath.split(":")) { + try { + StructStat sb = Libcore.os.stat(path); + if (!directoriesOnly || S_ISDIR(sb.st_mode)) { + resultList.add(new File(path)); + } + } catch (ErrnoException ignored) { + } + } + } + + /** + * Makes an array of dex/resource path elements, one per element of + * the given array. + */ + private static Element[] makeDexElements(ArrayList<File> files, + File optimizedDirectory) { + ArrayList<Element> elements = new ArrayList<Element>(); + + /* + * Open all files and load the (direct or contained) dex files + * up front. + */ + for (File file : files) { + File zip = null; + DexFile dex = null; + String name = file.getName(); + + if (name.endsWith(DEX_SUFFIX)) { + // Raw dex file (not inside a zip/jar). + try { + dex = loadDexFile(file, optimizedDirectory); + } catch (IOException ex) { + System.logE("Unable to load dex file: " + file, ex); + } + } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) + || name.endsWith(ZIP_SUFFIX)) { + zip = file; + + try { + dex = loadDexFile(file, optimizedDirectory); + } catch (IOException ignored) { + /* + * IOException might get thrown "legitimately" by + * the DexFile constructor if the zip file turns + * out to be resource-only (that is, no + * classes.dex file in it). Safe to just ignore + * the exception here, and let dex == null. + */ + } + } else if (file.isDirectory()) { + // We support directories for looking up resources. + // This is only useful for running libcore tests. + elements.add(new Element(file, true, null, null)); + } else { + System.logW("Unknown file type for: " + file); + } + + if ((zip != null) || (dex != null)) { + elements.add(new Element(file, false, zip, dex)); + } + } + + return elements.toArray(new Element[elements.size()]); + } + + /** + * Constructs a {@code DexFile} instance, as appropriate depending + * on whether {@code optimizedDirectory} is {@code null}. + */ + private static DexFile loadDexFile(File file, File optimizedDirectory) + throws IOException { + if (optimizedDirectory == null) { + return new DexFile(file); + } else { + String optimizedPath = optimizedPathFor(file, optimizedDirectory); + return DexFile.loadDex(file.getPath(), optimizedPath, 0); + } + } + + /** + * Converts a dex/jar file path and an output directory to an + * output file path for an associated optimized dex file. + */ + private static String optimizedPathFor(File path, + File optimizedDirectory) { + /* + * Get the filename component of the path, and replace the + * suffix with ".dex" if that's not already the suffix. + * + * We don't want to use ".odex", because the build system uses + * that for files that are paired with resource-only jar + * files. If the VM can assume that there's no classes.dex in + * the matching jar, it doesn't need to open the jar to check + * for updated dependencies, providing a slight performance + * boost at startup. The use of ".dex" here matches the use on + * files in /data/dalvik-cache. + */ + String fileName = path.getName(); + if (!fileName.endsWith(DEX_SUFFIX)) { + int lastDot = fileName.lastIndexOf("."); + if (lastDot < 0) { + fileName += DEX_SUFFIX; + } else { + StringBuilder sb = new StringBuilder(lastDot + 4); + sb.append(fileName, 0, lastDot); + sb.append(DEX_SUFFIX); + fileName = sb.toString(); + } + } + + File result = new File(optimizedDirectory, fileName); + return result.getPath(); + } + + /** + * Finds the named class in one of the dex files pointed at by + * this instance. This will find the one in the earliest listed + * path element. If the class is found but has not yet been + * defined, then this method will define it in the defining + * context that this instance was constructed with. + * + * @return the named class or {@code null} if the class is not + * found in any of the dex files + */ + public Class findClass(String name) { + for (Element element : dexElements) { + DexFile dex = element.dexFile; + + if (dex != null) { + Class clazz = dex.loadClassBinaryName(name, definingContext); + if (clazz != null) { + return clazz; + } + } + } + + return null; + } + + /** + * Finds the named resource in one of the zip/jar files pointed at + * by this instance. This will find the one in the earliest listed + * path element. + * + * @return a URL to the named resource or {@code null} if the + * resource is not found in any of the zip/jar files + */ + public URL findResource(String name) { + for (Element element : dexElements) { + URL url = element.findResource(name); + if (url != null) { + return url; + } + } + + return null; + } + + /** + * Finds all the resources with the given name, returning an + * enumeration of them. If there are no resources with the given + * name, then this method returns an empty enumeration. + */ + public Enumeration<URL> findResources(String name) { + ArrayList<URL> result = new ArrayList<URL>(); + + for (Element element : dexElements) { + URL url = element.findResource(name); + if (url != null) { + result.add(url); + } + } + + return Collections.enumeration(result); + } + + /** + * Finds the named native code library on any of the library + * directories pointed at by this instance. This will find the + * one in the earliest listed directory, ignoring any that are not + * readable regular files. + * + * @return the complete path to the library or {@code null} if no + * library was found + */ + public String findLibrary(String libraryName) { + String fileName = System.mapLibraryName(libraryName); + for (File directory : nativeLibraryDirectories) { + String path = new File(directory, fileName).getPath(); + if (IoUtils.canOpenReadOnly(path)) { + return path; + } + } + return null; + } + + /** + * Element of the dex/resource file path + */ + /*package*/ static class Element { + private final File file; + private final boolean isDirectory; + private final File zip; + private final DexFile dexFile; + + private ZipFile zipFile; + private boolean initialized; + + public Element(File file, boolean isDirectory, File zip, DexFile dexFile) { + this.file = file; + this.isDirectory = isDirectory; + this.zip = zip; + this.dexFile = dexFile; + } + + @Override public String toString() { + if (isDirectory) { + return "directory \"" + file + "\""; + } else if (zip != null) { + return "zip file \"" + zip + "\""; + } else { + return "dex file \"" + dexFile + "\""; + } + } + + public synchronized void maybeInit() { + if (initialized) { + return; + } + + initialized = true; + + if (isDirectory || zip == null) { + return; + } + + try { + zipFile = new ZipFile(zip); + } catch (IOException ioe) { + /* + * Note: ZipException (a subclass of IOException) + * might get thrown by the ZipFile constructor + * (e.g. if the file isn't actually a zip/jar + * file). + */ + System.logE("Unable to open zip file: " + file, ioe); + zipFile = null; + } + } + + public URL findResource(String name) { + maybeInit(); + + // We support directories so we can run tests and/or legacy code + // that uses Class.getResource. + if (isDirectory) { + File resourceFile = new File(file, name); + if (resourceFile.exists()) { + try { + return resourceFile.toURI().toURL(); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } + } + + if (zipFile == null || zipFile.getEntry(name) == null) { + /* + * Either this element has no zip/jar file (first + * clause), or the zip/jar file doesn't have an entry + * for the given name (second clause). + */ + return null; + } + + try { + /* + * File.toURL() is compliant with RFC 1738 in + * always creating absolute path names. If we + * construct the URL by concatenating strings, we + * might end up with illegal URLs for relative + * names. + */ + return new URL("jar:" + file.toURL() + "!/" + name); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/NativeStart.java b/dalvik/src/main/java/dalvik/system/NativeStart.java new file mode 100644 index 0000000..1382823 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/NativeStart.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 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 dalvik.system; + +/** + * Dummy class used during JNI initialization. The JNI functions want + * to be able to create objects, and the VM needs to discard the references + * when the function returns. That gets a little weird when we're + * calling JNI functions from the C main(), and there's no Java stack frame + * to hitch the references onto. + * + * Rather than having some special-case code, we create this simple little + * class and pretend that it called the C main(). + * + * This also comes in handy when a native thread attaches itself with the + * JNI AttachCurrentThread call. If they attach the thread and start + * creating objects, we need a fake frame to store stuff in. + */ +class NativeStart { + private NativeStart() {} + + private static native void main(String[] dummy); + + private static native void run(); +} diff --git a/dalvik/src/main/java/dalvik/system/PathClassLoader.java b/dalvik/src/main/java/dalvik/system/PathClassLoader.java new file mode 100644 index 0000000..32c5586 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/PathClassLoader.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 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 dalvik.system; + +/** + * Provides a simple {@link ClassLoader} implementation that operates on a list + * of files and directories in the local file system, but does not attempt to + * load classes from the network. Android uses this class for its system class + * loader and for its application class loader(s). + */ +public class PathClassLoader extends BaseDexClassLoader { + /** + * Creates a {@code PathClassLoader} that operates on a given list of files + * and directories. This method is equivalent to calling + * {@link #PathClassLoader(String, String, ClassLoader)} with a + * {@code null} value for the second argument (see description there). + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param parent the parent class loader + */ + public PathClassLoader(String dexPath, ClassLoader parent) { + super(dexPath, null, null, parent); + } + + /** + * Creates a {@code PathClassLoader} that operates on two given + * lists of files and directories. The entries of the first list + * should be one of the following: + * + * <ul> + * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as + * well as arbitrary resources. + * <li>Raw ".dex" files (not inside a zip file). + * </ul> + * + * The entries of the second list should be directories containing + * native library files. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public PathClassLoader(String dexPath, String libraryPath, + ClassLoader parent) { + super(dexPath, null, libraryPath, parent); + } +} diff --git a/dalvik/src/main/java/dalvik/system/PotentialDeadlockError.java b/dalvik/src/main/java/dalvik/system/PotentialDeadlockError.java new file mode 100644 index 0000000..7a18bb5 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/PotentialDeadlockError.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 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 dalvik.system; + +/** + * Is thrown when the VM identifies a potential deadlock. + * + * @hide + */ +public class PotentialDeadlockError extends VirtualMachineError { + /** + * Creates a new exception instance and initializes it with default values. + */ + public PotentialDeadlockError() { + super(); + } + + /** + * Creates a new exception instance and initializes it with a given message. + * + * @param detailMessage the error message + */ + public PotentialDeadlockError(String detailMessage) { + super(detailMessage); + } +} + diff --git a/dalvik/src/main/java/dalvik/system/SocketTagger.java b/dalvik/src/main/java/dalvik/system/SocketTagger.java new file mode 100644 index 0000000..75242ce --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/SocketTagger.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 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 dalvik.system; + +import java.io.FileDescriptor; +import java.net.Socket; +import java.net.SocketException; + +/** + * Callbacks for socket assignment and reassignment. + * + * @hide + */ +public abstract class SocketTagger { + + private static SocketTagger tagger = new SocketTagger() { + @Override public void tag(FileDescriptor socketDescriptor) throws SocketException {} + @Override public void untag(FileDescriptor socketDescriptor) throws SocketException {} + }; + + /** + * Notified when {@code socketDescriptor} is either assigned to the current + * thread. The socket is either newly connected or reused from a connection + * pool. Implementations of this method should be thread-safe. + */ + public abstract void tag(FileDescriptor socketDescriptor) throws SocketException; + + /** + * Notified when {@code socketDescriptor} is released from the current + * thread to a connection pool. Implementations of this method should be + * thread-safe. + * + * <p><strong>Note:</strong> this method will not be invoked when the socket + * is closed. + */ + public abstract void untag(FileDescriptor socketDescriptor) throws SocketException; + + public final void tag(Socket socket) throws SocketException { + if (!socket.isClosed()) { + tag(socket.getFileDescriptor$()); + } + } + + public final void untag(Socket socket) throws SocketException { + if (!socket.isClosed()) { + untag(socket.getFileDescriptor$()); + } + } + + /** + * Sets this process' socket tagger to {@code tagger}. + */ + public static synchronized void set(SocketTagger tagger) { + if (tagger == null) { + throw new NullPointerException("tagger == null"); + } + SocketTagger.tagger = tagger; + } + + /** + * Returns this process socket tagger. + */ + public static synchronized SocketTagger get() { + return tagger; + } +} diff --git a/dalvik/src/main/java/dalvik/system/StaleDexCacheError.java b/dalvik/src/main/java/dalvik/system/StaleDexCacheError.java new file mode 100644 index 0000000..8bdd86a --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/StaleDexCacheError.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 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 dalvik.system; + +/** + * Is thrown when the VM determines that a DEX file's cache is out of date, and + * that there is no way to recreate it. + * + * @hide + */ +public class StaleDexCacheError extends VirtualMachineError { + /** + * Creates a new exception instance and initializes it with default values. + */ + public StaleDexCacheError() { + super(); + } + + /** + * Creates a new exception instance and initializes it with a given message. + * + * @param detailMessage the error message + */ + public StaleDexCacheError(String detailMessage) { + super(detailMessage); + } +} diff --git a/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java b/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java new file mode 100644 index 0000000..f8fb0b1 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 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 dalvik.system; + +import java.io.File; + +/** + * Obsolete, for binary compatibility only. + * + * @hide + */ +public class TemporaryDirectory { + /** + * This method exists for binary compatibility only. + */ + public static void setUpDirectory(String baseDir) { + } + + /** + * This method exists for binary compatibility only. + */ + public static synchronized void setUpDirectory(File baseDir) { + } +} diff --git a/dalvik/src/main/java/dalvik/system/VMDebug.java b/dalvik/src/main/java/dalvik/system/VMDebug.java new file mode 100644 index 0000000..8f40165 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/VMDebug.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2007 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 dalvik.system; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * Provides access to some VM-specific debug features. Though this class and + * many of its members are public, this class is meant to be wrapped in a more + * friendly way for use by application developers. On the Android platform, the + * recommended way to access this functionality is through the class + * <code>android.os.Debug</code>. + * + * @hide + */ +public final class VMDebug { + /** + * Specifies the default method trace data file name. + * + * @deprecated only used in one place, which is unused and deprecated + */ + @Deprecated + static public final String DEFAULT_METHOD_TRACE_FILE_NAME = "/sdcard/dmtrace.trace"; + + /** + * flag for startMethodTracing(), which adds the results from + * startAllocCounting to the trace key file. + */ + public static final int TRACE_COUNT_ALLOCS = 1; + + /* constants for getAllocCount */ + private static final int KIND_ALLOCATED_OBJECTS = 1<<0; + private static final int KIND_ALLOCATED_BYTES = 1<<1; + private static final int KIND_FREED_OBJECTS = 1<<2; + private static final int KIND_FREED_BYTES = 1<<3; + private static final int KIND_GC_INVOCATIONS = 1<<4; + private static final int KIND_CLASS_INIT_COUNT = 1<<5; + private static final int KIND_CLASS_INIT_TIME = 1<<6; + private static final int KIND_EXT_ALLOCATED_OBJECTS = 1<<12; + private static final int KIND_EXT_ALLOCATED_BYTES = 1<<13; + private static final int KIND_EXT_FREED_OBJECTS = 1<<14; + private static final int KIND_EXT_FREED_BYTES = 1<<15; + + public static final int KIND_GLOBAL_ALLOCATED_OBJECTS = + KIND_ALLOCATED_OBJECTS; + public static final int KIND_GLOBAL_ALLOCATED_BYTES = + KIND_ALLOCATED_BYTES; + public static final int KIND_GLOBAL_FREED_OBJECTS = + KIND_FREED_OBJECTS; + public static final int KIND_GLOBAL_FREED_BYTES = + KIND_FREED_BYTES; + public static final int KIND_GLOBAL_GC_INVOCATIONS = + KIND_GC_INVOCATIONS; + public static final int KIND_GLOBAL_CLASS_INIT_COUNT = + KIND_CLASS_INIT_COUNT; + public static final int KIND_GLOBAL_CLASS_INIT_TIME = + KIND_CLASS_INIT_TIME; + public static final int KIND_GLOBAL_EXT_ALLOCATED_OBJECTS = + KIND_EXT_ALLOCATED_OBJECTS; + public static final int KIND_GLOBAL_EXT_ALLOCATED_BYTES = + KIND_EXT_ALLOCATED_BYTES; + public static final int KIND_GLOBAL_EXT_FREED_OBJECTS = + KIND_EXT_FREED_OBJECTS; + public static final int KIND_GLOBAL_EXT_FREED_BYTES = + KIND_EXT_FREED_BYTES; + + public static final int KIND_THREAD_ALLOCATED_OBJECTS = + KIND_ALLOCATED_OBJECTS << 16; + public static final int KIND_THREAD_ALLOCATED_BYTES = + KIND_ALLOCATED_BYTES << 16; + public static final int KIND_THREAD_FREED_OBJECTS = + KIND_FREED_OBJECTS << 16; + public static final int KIND_THREAD_FREED_BYTES = + KIND_FREED_BYTES << 16; + public static final int KIND_THREAD_GC_INVOCATIONS = + KIND_GC_INVOCATIONS << 16; + public static final int KIND_THREAD_CLASS_INIT_COUNT = + KIND_CLASS_INIT_COUNT << 16; + public static final int KIND_THREAD_CLASS_INIT_TIME = + KIND_CLASS_INIT_TIME << 16; + public static final int KIND_THREAD_EXT_ALLOCATED_OBJECTS = + KIND_EXT_ALLOCATED_OBJECTS << 16; + public static final int KIND_THREAD_EXT_ALLOCATED_BYTES = + KIND_EXT_ALLOCATED_BYTES << 16; + public static final int KIND_THREAD_EXT_FREED_OBJECTS = + KIND_EXT_FREED_OBJECTS << 16; + public static final int KIND_THREAD_EXT_FREED_BYTES = + KIND_EXT_FREED_BYTES << 16; + + public static final int KIND_ALL_COUNTS = 0xffffffff; + + /* all methods are static */ + private VMDebug() {} + + /** + * Returns the time since the last known debugger activity. + * + * @return the time in milliseconds, or -1 if the debugger is not connected + */ + public static native long lastDebuggerActivity(); + + /** + * Determines if debugging is enabled in this VM. If debugging is not + * enabled, a debugger cannot be attached. + * + * @return true if debugging is enabled + */ + public static native boolean isDebuggingEnabled(); + + /** + * Determines if a debugger is currently attached. + * + * @return true if (and only if) a debugger is connected + */ + public static native boolean isDebuggerConnected(); + + /** + * Returns an array of strings that identify VM features. This is + * used by DDMS to determine what sorts of operations the VM can + * perform. + */ + public static native String[] getVmFeatureList(); + + /** + * Start method tracing with default name, size, and with <code>0</code> + * flags. + * + * @deprecated not used, not needed + */ + @Deprecated + public static void startMethodTracing() { + startMethodTracing(DEFAULT_METHOD_TRACE_FILE_NAME, 0, 0); + } + + /** + * Start method tracing, specifying a file name as well as a default + * buffer size. See <a + * href="{@docRoot}guide/developing/tools/traceview.html"> Running the + * Traceview Debugging Program</a> for information about reading + * trace files. + * + * <p>You can use either a fully qualified path and + * name, or just a name. If only a name is specified, the file will + * be created under the /sdcard/ directory. If a name is not given, + * the default is /sdcard/dmtrace.trace.</p> + * + * @param traceFileName name to give the trace file + * @param bufferSize the maximum size of both files combined. If passed + * as <code>0</code>, it defaults to 8MB. + * @param flags flags to control method tracing. The only one that + * is currently defined is {@link #TRACE_COUNT_ALLOCS}. + */ + public static void startMethodTracing(String traceFileName, int bufferSize, int flags) { + + if (traceFileName == null) { + throw new NullPointerException("traceFileName == null"); + } + + startMethodTracingNative(traceFileName, null, bufferSize, flags); + } + + /** + * Like startMethodTracing(String, int, int), but taking an already-opened + * FileDescriptor in which the trace is written. The file name is also + * supplied simply for logging. Makes a dup of the file descriptor. + */ + public static void startMethodTracing(String traceFileName, + FileDescriptor fd, int bufferSize, int flags) + { + if (traceFileName == null) { + throw new NullPointerException("traceFileName == null"); + } + if (fd == null) { + throw new NullPointerException("fd == null"); + } + + startMethodTracingNative(traceFileName, fd, bufferSize, flags); + } + + /** + * Starts method tracing without a backing file. When stopMethodTracing + * is called, the result is sent directly to DDMS. (If DDMS is not + * attached when tracing ends, the profiling data will be discarded.) + */ + public static void startMethodTracingDdms(int bufferSize, int flags) { + startMethodTracingNative(null, null, bufferSize, flags); + } + + /** + * Implements all startMethodTracing variants. + */ + private static native void startMethodTracingNative(String traceFileName, + FileDescriptor fd, int bufferSize, int flags); + + /** + * Determine whether method tracing is currently active. + */ + public static native boolean isMethodTracingActive(); + + /** + * Stops method tracing. + */ + public static native void stopMethodTracing(); + + /** + * Starts sending Dalvik method trace info to the emulator. + */ + public static native void startEmulatorTracing(); + + /** + * Stops sending Dalvik method trace info to the emulator. + */ + public static native void stopEmulatorTracing(); + + /** + * Get an indication of thread CPU usage. The value returned indicates the + * amount of time that the current thread has spent executing code or + * waiting for certain types of I/O. + * <p> + * The time is expressed in nanoseconds, and is only meaningful when + * compared to the result from an earlier call. Note that nanosecond + * resolution does not imply nanosecond accuracy. + * + * @return the CPU usage. A value of -1 means the system does not support + * this feature. + */ + public static native long threadCpuTimeNanos(); + + /** + * Count the number and aggregate size of memory allocations between + * two points. + */ + public static native void startAllocCounting(); + public static native void stopAllocCounting(); + public static native int getAllocCount(int kind); + public static native void resetAllocCount(int kinds); + + /** + * This method exists for binary compatibility. It was part of + * the allocation limits API which was removed in Honeycomb. + */ + @Deprecated + public static int setAllocationLimit(int limit) { + return -1; + } + + /** + * This method exists for binary compatibility. It was part of + * the allocation limits API which was removed in Honeycomb. + */ + @Deprecated + public static int setGlobalAllocationLimit(int limit) { + return -1; + } + + /** + * Count the number of instructions executed between two points. + */ + public static native void startInstructionCounting(); + public static native void stopInstructionCounting(); + public static native void getInstructionCount(int[] counts); + public static native void resetInstructionCount(); + + /** + * Dumps a list of loaded class to the log file. + */ + public static native void printLoadedClasses(int flags); + + /** + * Gets the number of loaded classes. + * + * @return the number of loaded classes + */ + public static native int getLoadedClassCount(); + + /** + * Dumps "hprof" data to the specified file. This may cause a GC. + * + * The VM may create a temporary file in the same directory. + * + * @param filename Full pathname of output file (e.g. "/sdcard/dump.hprof"). + * @throws UnsupportedOperationException if the VM was built without + * HPROF support. + * @throws IOException if an error occurs while opening or writing files. + */ + public static void dumpHprofData(String filename) throws IOException { + if (filename == null) { + throw new NullPointerException("filename == null"); + } + dumpHprofData(filename, null); + } + + /** + * Collects "hprof" heap data and sends it to DDMS. This may cause a GC. + * + * @throws UnsupportedOperationException if the VM was built without + * HPROF support. + */ + public static native void dumpHprofDataDdms(); + + /** + * Dumps "hprof" heap data to a file, by name or descriptor. + * + * @param fileName Name of output file. If fd is non-null, the + * file name is only used in log messages (and may be null). + * @param fd Descriptor of open file that will receive the output. + * If this is null, the fileName is used instead. + */ + public static native void dumpHprofData(String fileName, FileDescriptor fd) + throws IOException; + + /** + * Primes the register map cache. + */ + public static native boolean cacheRegisterMap(String classAndMethodDesc); + + /** + * Dumps the contents of the VM reference tables (e.g. JNI locals and + * globals) to the log file. + */ + public static native void dumpReferenceTables(); + + /** + * Crashes the VM. Seriously. Dumps the interpreter stack trace for + * the current thread and then aborts the VM so you can see the native + * stack trace. Useful for figuring out how you got somewhere when + * lots of native code is involved. + */ + public static native void crash(); + + /** + * Together with gdb, provide a handy way to stop the VM at user-tagged + * locations. + */ + public static native void infopoint(int id); + + /* + * Fake method, inserted into dmtrace output when the garbage collector + * runs. Not actually called. + */ + private static void startGC() {} + + /* + * Fake method, inserted into dmtrace output during class preparation + * (loading and linking, but not verification or initialization). Not + * actually called. + */ + private static void startClassPrep() {} + + /** + * Counts the instances of a class. + * + * @param klass the class to be counted. + * @param assignable if false, direct instances of klass are + * counted. If true, instances that are + * assignable to klass, as defined by + * {@link Class#isAssignableFrom} are counted. + * @return the number of matching instances. + */ + public static native long countInstancesOfClass(Class klass, boolean assignable); +} diff --git a/dalvik/src/main/java/dalvik/system/VMRuntime.java b/dalvik/src/main/java/dalvik/system/VMRuntime.java new file mode 100644 index 0000000..71098be --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/VMRuntime.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2007 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 dalvik.system; + +/** + * Provides an interface to VM-global, Dalvik-specific features. + * An application cannot create its own Runtime instance, and must obtain + * one from the getRuntime method. + * + * @hide + */ +public final class VMRuntime { + + /** + * Holds the VMRuntime singleton. + */ + private static final VMRuntime THE_ONE = new VMRuntime(); + + /** + * Prevents this class from being instantiated. + */ + private VMRuntime() { + } + + /** + * Returns the object that represents the VM instance's Dalvik-specific + * runtime environment. + * + * @return the runtime object + */ + public static VMRuntime getRuntime() { + return THE_ONE; + } + + /** + * Returns a copy of the VM's command-line property settings. + * These are in the form "name=value" rather than "-Dname=value". + */ + public native String[] properties(); + + /** + * Returns the VM's boot class path. + */ + public native String bootClassPath(); + + /** + * Returns the VM's class path. + */ + public native String classPath(); + + /** + * Returns the VM's version. + */ + public native String vmVersion(); + + /** + * Gets the current ideal heap utilization, represented as a number + * between zero and one. After a GC happens, the Dalvik heap may + * be resized so that (size of live objects) / (size of heap) is + * equal to this number. + * + * @return the current ideal heap utilization + */ + public native float getTargetHeapUtilization(); + + /** + * Sets the current ideal heap utilization, represented as a number + * between zero and one. After a GC happens, the Dalvik heap may + * be resized so that (size of live objects) / (size of heap) is + * equal to this number. + * + * <p>This is only a hint to the garbage collector and may be ignored. + * + * @param newTarget the new suggested ideal heap utilization. + * This value may be adjusted internally. + * @return the previous ideal heap utilization + * @throws IllegalArgumentException if newTarget is <= 0.0 or >= 1.0 + */ + public float setTargetHeapUtilization(float newTarget) { + if (newTarget <= 0.0f || newTarget >= 1.0f) { + throw new IllegalArgumentException(newTarget + + " out of range (0,1)"); + } + /* Synchronize to make sure that only one thread gets + * a given "old" value if both update at the same time. + * Allows for reliable save-and-restore semantics. + */ + synchronized (this) { + float oldTarget = getTargetHeapUtilization(); + nativeSetTargetHeapUtilization(newTarget); + return oldTarget; + } + } + + /** + * Sets the target SDK version. Should only be called before the + * app starts to run, because it may change the VM's behavior in + * dangerous ways. Use 0 to mean "current" (since callers won't + * necessarily know the actual current SDK version, and the + * allocated version numbers start at 1). + */ + public native void setTargetSdkVersion(int targetSdkVersion); + + /** + * This method exists for binary compatibility. It was part of a + * heap sizing API which was removed in Honeycomb. + */ + @Deprecated + public long getMinimumHeapSize() { + return 0; + } + + /** + * This method exists for binary compatibility. It was part of a + * heap sizing API which was removed in Honeycomb. + */ + @Deprecated + public long setMinimumHeapSize(long size) { + return 0; + } + + /** + * This method exists for binary compatibility. It used to + * perform a garbage collection that cleared SoftReferences. + */ + @Deprecated + public void gcSoftReferences() {} + + /** + * This method exists for binary compatibility. It is equivalent + * to {@link System#runFinalization}. + */ + @Deprecated + public void runFinalizationSync() { + System.runFinalization(); + } + + /** + * Implements setTargetHeapUtilization(). + * + * @param newTarget the new suggested ideal heap utilization. + * This value may be adjusted internally. + */ + private native void nativeSetTargetHeapUtilization(float newTarget); + + /** + * This method exists for binary compatibility. It was part of + * the external allocation API which was removed in Honeycomb. + */ + @Deprecated + public boolean trackExternalAllocation(long size) { + return true; + } + + /** + * This method exists for binary compatibility. It was part of + * the external allocation API which was removed in Honeycomb. + */ + @Deprecated + public void trackExternalFree(long size) {} + + /** + * This method exists for binary compatibility. It was part of + * the external allocation API which was removed in Honeycomb. + */ + @Deprecated + public long getExternalBytesAllocated() { + return 0; + } + + /** + * Tells the VM to enable the JIT compiler. If the VM does not have a JIT + * implementation, calling this method should have no effect. + */ + public native void startJitCompilation(); + + /** + * Tells the VM to disable the JIT compiler. If the VM does not have a JIT + * implementation, calling this method should have no effect. + */ + public native void disableJitCompilation(); + + /** + * Returns an array allocated in an area of the Java heap where it will never be moved. + * This is used to implement native allocations on the Java heap, such as DirectByteBuffers + * and Bitmaps. + */ + public native Object newNonMovableArray(Class<?> componentType, int length); + + /** + * Returns the address of array[0]. This differs from using JNI in that JNI might lie and + * give you the address of a copy of the array when in forcecopy mode. + */ + public native long addressOf(Object array); + + /** + * Removes any growth limits, allowing the application to allocate + * up to the maximum heap size. + */ + public native void clearGrowthLimit(); + + /** + * Returns true if either a Java debugger or native debugger is active. + */ + public native boolean isDebuggerActive(); +} diff --git a/dalvik/src/main/java/dalvik/system/VMStack.java b/dalvik/src/main/java/dalvik/system/VMStack.java new file mode 100644 index 0000000..9a2be23 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/VMStack.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2007 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 dalvik.system; + +/** + * Provides a limited interface to the Dalvik VM stack. This class is mostly + * used for implementing security checks. + * + * @hide + */ +public final class VMStack { + /** + * Returns the defining class loader of the caller's caller. + * + * @return the requested class loader, or {@code null} if this is the + * bootstrap class loader. + */ + native public static ClassLoader getCallingClassLoader(); + + /** + * Returns the class of the caller's caller's caller. + * + * @return the requested class, or {@code null}. + */ + native public static Class<?> getStackClass2(); + + /** + * Creates an array of classes from the methods at the top of the stack. + * We continue until we reach the bottom of the stack or exceed the + * specified maximum depth. + * <p> + * The topmost stack frame (this method) and the one above that (the + * caller) are excluded from the array. Frames with java.lang.reflect + * classes are skipped over. + * <p> + * The classes in the array are the defining classes of the methods. + * <p> + * This is similar to Harmony's VMStack.getClasses, except that this + * implementation doesn't have a concept of "privileged" frames. + * + * @param maxDepth + * maximum number of classes to return, or -1 for all + * @return an array with classes for the most-recent methods on the stack + */ + native public static Class<?>[] getClasses(int maxDepth); + + /** + * Retrieves the stack trace from the specified thread. + * + * @param t + * thread of interest + * @return an array of stack trace elements, or null if the thread + * doesn't have a stack trace (e.g. because it exited) + */ + native public static StackTraceElement[] getThreadStackTrace(Thread t); + + /** + * Retrieves a partial stack trace from the specified thread into + * the provided array. + * + * @param t + * thread of interest + * @param stackTraceElements + * preallocated array for use when only the top of stack is + * desired. Unused elements will be filled with null values. + * @return the number of elements filled + */ + native public static int fillStackTraceElements(Thread t, + StackTraceElement[] stackTraceElements); +} diff --git a/dalvik/src/main/java/dalvik/system/Zygote.java b/dalvik/src/main/java/dalvik/system/Zygote.java new file mode 100644 index 0000000..9e96204 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/Zygote.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2006 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 dalvik.system; + +import libcore.io.ErrnoException; +import libcore.io.Libcore; + +import java.io.File; + +/** + * Provides access to the Dalvik "zygote" feature, which allows a VM instance to + * be partially initialized and then fork()'d from the partially initialized + * state. + * + * @hide + */ +public class Zygote { + /* + * Bit values for "debugFlags" argument. The definitions are duplicated + * in the native code. + */ + /** enable debugging over JDWP */ + public static final int DEBUG_ENABLE_DEBUGGER = 1; + /** enable JNI checks */ + public static final int DEBUG_ENABLE_CHECKJNI = 1 << 1; + /** enable Java programming language "assert" statements */ + public static final int DEBUG_ENABLE_ASSERT = 1 << 2; + /** disable the JIT compiler */ + public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3; + /** Enable logging of third-party JNI activity. */ + public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; + + /** No external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_NONE = 0; + /** Single-user external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_SINGLEUSER = 1; + /** Multi-user external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_MULTIUSER = 2; + /** All multi-user external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3; + + /** + * When set by the system server, all subsequent apps will be launched in + * VM safe mode. + */ + public static boolean systemInSafeMode = false; + + private Zygote() {} + + private static void preFork() { + Daemons.stop(); + waitUntilAllThreadsStopped(); + } + + /** + * We must not fork until we're single-threaded again. Wait until /proc shows we're + * down to just one thread. + */ + private static void waitUntilAllThreadsStopped() { + File tasks = new File("/proc/self/task"); + while (tasks.list().length > 1) { + try { + // Experimentally, booting and playing about with a stingray, I never saw us + // go round this loop more than once with a 10ms sleep. + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } + } + + private static void postFork() { + Daemons.start(); + } + + /** + * Forks a new Zygote instance, but does not leave the zygote mode. + * The current VM must have been started with the -Xzygote flag. The + * new child is expected to eventually call forkAndSpecialize() + * + * @return 0 if this is the child, pid of the child + * if this is the parent, or -1 on error + */ + public static int fork() { + preFork(); + int pid = nativeFork(); + postFork(); + return pid; + } + + native public static int nativeFork(); + + /** + * Forks a new VM instance. The current VM must have been started + * with the -Xzygote flag. <b>NOTE: new instance keeps all + * root capabilities. The new process is expected to call capset()</b>. + * + * @param uid the UNIX uid that the new process should setuid() to after + * fork()ing and and before spawning any threads. + * @param gid the UNIX gid that the new process should setgid() to after + * fork()ing and and before spawning any threads. + * @param gids null-ok; a list of UNIX gids that the new process should + * setgroups() to after fork and before spawning any threads. + * @param debugFlags bit flags that enable debugging features. + * @param rlimits null-ok an array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param seInfo null-ok a string specifying SEAndroid information for + * the new process. + * @param niceName null-ok a string specifying the process name. + * + * @return 0 if this is the child, pid of the child + * if this is the parent, or -1 on error. + */ + public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName) { + preFork(); + int pid = nativeForkAndSpecialize( + uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName); + postFork(); + return pid; + } + + native public static int nativeForkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName); + + /** + * Special method to start the system server process. In addition to the + * common actions performed in forkAndSpecialize, the pid of the child + * process is recorded such that the death of the child process will cause + * zygote to exit. + * + * @param uid the UNIX uid that the new process should setuid() to after + * fork()ing and and before spawning any threads. + * @param gid the UNIX gid that the new process should setgid() to after + * fork()ing and and before spawning any threads. + * @param gids null-ok; a list of UNIX gids that the new process should + * setgroups() to after fork and before spawning any threads. + * @param debugFlags bit flags that enable debugging features. + * @param rlimits null-ok an array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param permittedCapabilities argument for setcap() + * @param effectiveCapabilities argument for setcap() + * + * @return 0 if this is the child, pid of the child + * if this is the parent, or -1 on error. + */ + public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { + preFork(); + int pid = nativeForkSystemServer( + uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities); + postFork(); + return pid; + } + + native public static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); + + /** + * Executes "/system/bin/sh -c <command>" using the exec() system call. + * This method throws a runtime exception if exec() failed, otherwise, this + * method never returns. + * + * @param command The shell command to execute. + */ + public static void execShell(String command) { + String[] args = { "/system/bin/sh", "-c", command }; + try { + Libcore.os.execv(args[0], args); + } catch (ErrnoException e) { + throw new RuntimeException(e); + } + } + + /** + * Appends quotes shell arguments to the specified string builder. + * The arguments are quoted using single-quotes, escaped if necessary, + * prefixed with a space, and appended to the command. + * + * @param command A string builder for the shell command being constructed. + * @param args An array of argument strings to be quoted and appended to the command. + * @see #execShell(String) + */ + public static void appendQuotedShellArgs(StringBuilder command, String[] args) { + for (String arg : args) { + command.append(" '").append(arg.replace("'", "'\\''")).append("'"); + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/AsciiHprofWriter.java b/dalvik/src/main/java/dalvik/system/profiler/AsciiHprofWriter.java new file mode 100644 index 0000000..dbddcc6 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/AsciiHprofWriter.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +/** + * AsciiHprofWriter produces hprof compatible text output for use with + * third party tools such as PerfAnal. + */ +public final class AsciiHprofWriter { + + private final HprofData data; + private final PrintWriter out; + + /** + * Writes the provided data to the specified stream. + */ + public static void write(HprofData data, OutputStream outputStream) throws IOException { + new AsciiHprofWriter(data, outputStream).write(); + } + + private AsciiHprofWriter(HprofData data, OutputStream outputStream) { + this.data = data; + this.out = new PrintWriter(outputStream); + } + + private void write() throws IOException { + for (HprofData.ThreadEvent e : data.getThreadHistory()) { + out.println(e); + } + + List<HprofData.Sample> samples + = new ArrayList<HprofData.Sample>(data.getSamples()); + Collections.sort(samples, SAMPLE_COMPARATOR); + int total = 0; + for (HprofData.Sample sample : samples) { + HprofData.StackTrace stackTrace = sample.stackTrace; + int count = sample.count; + total += count; + out.printf("TRACE %d: (thread=%d)\n", + stackTrace.stackTraceId, + stackTrace.threadId); + for (StackTraceElement e : stackTrace.stackFrames) { + out.printf("\t%s\n", e); + } + } + Date now = new Date(data.getStartMillis()); + // "CPU SAMPLES BEGIN (total = 826) Wed Jul 21 12:03:46 2010" + out.printf("CPU SAMPLES BEGIN (total = %d) %ta %tb %td %tT %tY\n", + total, now, now, now, now, now); + out.printf("rank self accum count trace method\n"); + int rank = 0; + double accum = 0; + for (HprofData.Sample sample : samples) { + rank++; + HprofData.StackTrace stackTrace = sample.stackTrace; + int count = sample.count; + double self = (double)count/(double)total; + accum += self; + + // " 1 65.62% 65.62% 542 300302 java.lang.Long.parseLong" + out.printf("% 4d% 6.2f%%% 6.2f%% % 7d % 5d %s.%s\n", + rank, self*100, accum*100, count, stackTrace.stackTraceId, + stackTrace.stackFrames[0].getClassName(), + stackTrace.stackFrames[0].getMethodName()); + } + out.printf("CPU SAMPLES END\n"); + out.flush(); + } + + private static final Comparator<HprofData.Sample> SAMPLE_COMPARATOR + = new Comparator<HprofData.Sample>() { + public int compare(HprofData.Sample s1, HprofData.Sample s2) { + return s2.count - s1.count; + } + }; +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/BinaryHprof.java b/dalvik/src/main/java/dalvik/system/profiler/BinaryHprof.java new file mode 100644 index 0000000..9720446 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/BinaryHprof.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Hprof binary format related constants shared between the + * BinaryHprofReader and BinaryHprofWriter. + */ +public final class BinaryHprof { + /** + * Currently code only supports 4 byte id size. + */ + public static final int ID_SIZE = 4; + + /** + * Prefix of valid magic values from the start of a binary hprof file. + */ + static String MAGIC = "JAVA PROFILE "; + + /** + * Returns the file's magic value as a String if found, otherwise null. + */ + public static final String readMagic(DataInputStream in) { + try { + byte[] bytes = new byte[512]; + for (int i = 0; i < bytes.length; i++) { + byte b = in.readByte(); + if (b == '\0') { + String string = new String(bytes, 0, i, "UTF-8"); + if (string.startsWith(MAGIC)) { + return string; + } + return null; + } + bytes[i] = b; + } + return null; + } catch (IOException e) { + return null; + } + } + + public static enum Tag { + + STRING_IN_UTF8(0x01, -ID_SIZE), + LOAD_CLASS(0x02, 4 + ID_SIZE + 4 + ID_SIZE), + UNLOAD_CLASS(0x03, 4), + STACK_FRAME(0x04, ID_SIZE + ID_SIZE + ID_SIZE + ID_SIZE + 4 + 4), + STACK_TRACE(0x05, -(4 + 4 + 4)), + ALLOC_SITES(0x06, -(2 + 4 + 4 + 4 + 8 + 8 + 4)), + HEAP_SUMMARY(0x07, 4 + 4 + 8 + 8), + START_THREAD(0x0a, 4 + ID_SIZE + 4 + ID_SIZE + ID_SIZE + ID_SIZE), + END_THREAD(0x0b, 4), + HEAP_DUMP(0x0c, -0), + HEAP_DUMP_SEGMENT(0x1c, -0), + HEAP_DUMP_END(0x2c, 0), + CPU_SAMPLES(0x0d, -(4 + 4)), + CONTROL_SETTINGS(0x0e, 4 + 2); + + public final byte tag; + + /** + * Minimum size in bytes. + */ + public final int minimumSize; + + /** + * Maximum size in bytes. 0 mean no specific limit. + */ + public final int maximumSize; + + private Tag(int tag, int size) { + this.tag = (byte) tag; + if (size > 0) { + // fixed size, max and min the same + this.minimumSize = size; + this.maximumSize = size; + } else { + // only minimum bound + this.minimumSize = -size; + this.maximumSize = 0; + } + } + + private static final Map<Byte, Tag> BYTE_TO_TAG + = new HashMap<Byte, Tag>(); + + static { + for (Tag v : Tag.values()) { + BYTE_TO_TAG.put(v.tag, v); + } + } + + public static Tag get(byte tag) { + return BYTE_TO_TAG.get(tag); + } + + /** + * Returns null if the actual size meets expectations, or a + * String error message if not. + */ + public String checkSize(int actual) { + if (actual < minimumSize) { + return "expected a minimial record size of " + minimumSize + " for " + this + + " but received " + actual; + } + if (maximumSize == 0) { + return null; + } + if (actual > maximumSize) { + return "expected a maximum record size of " + maximumSize + " for " + this + + " but received " + actual; + } + return null; + } + } + + public static enum ControlSettings { + ALLOC_TRACES(0x01), + CPU_SAMPLING(0x02); + + public final int bitmask; + + private ControlSettings(int bitmask) { + this.bitmask = bitmask; + } + } + +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/BinaryHprofReader.java b/dalvik/src/main/java/dalvik/system/profiler/BinaryHprofReader.java new file mode 100644 index 0000000..75a17f3 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/BinaryHprofReader.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * <pre> {@code + * BinaryHprofReader reader = new BinaryHprofReader(new BufferedInputStream(inputStream)); + * reader.setStrict(false); // for RI compatability + * reader.read(); + * inputStream.close(); + * reader.getVersion(); + * reader.getHprofData(); + * }</pre> + */ +public final class BinaryHprofReader { + + private static final boolean TRACE = false; + + private final DataInputStream in; + + /** + * By default we try to strictly validate rules followed by + * our HprofWriter. For example, every end thread is preceded + * by a matching start thread. + */ + private boolean strict = true; + + /** + * version string from header after read has been performed, + * otherwise null. nullness used to detect if callers try to + * access data before read is called. + */ + private String version; + + private final Map<HprofData.StackTrace, int[]> stackTraces + = new HashMap<HprofData.StackTrace, int[]>(); + + private final HprofData hprofData = new HprofData(stackTraces); + + private final Map<Integer, String> idToString = new HashMap<Integer, String>(); + private final Map<Integer, String> idToClassName = new HashMap<Integer, String>(); + private final Map<Integer, StackTraceElement> idToStackFrame + = new HashMap<Integer, StackTraceElement>(); + private final Map<Integer, HprofData.StackTrace> idToStackTrace + = new HashMap<Integer, HprofData.StackTrace>(); + + /** + * Creates a BinaryHprofReader around the specified {@code + * inputStream} + */ + public BinaryHprofReader(InputStream inputStream) throws IOException { + this.in = new DataInputStream(inputStream); + } + + public boolean getStrict () { + return strict; + } + + public void setStrict (boolean strict) { + if (version != null) { + throw new IllegalStateException("cannot set strict after read()"); + } + this.strict = strict; + } + + /** + * throws IllegalStateException if read() has not been called. + */ + private void checkRead() { + if (version == null) { + throw new IllegalStateException("data access before read()"); + } + } + + public String getVersion() { + checkRead(); + return version; + } + + public HprofData getHprofData() { + checkRead(); + return hprofData; + } + + /** + * Read the hprof header and records from the input + */ + public void read() throws IOException { + parseHeader(); + parseRecords(); + } + + private void parseHeader() throws IOException { + if (TRACE) { + System.out.println("hprofTag=HEADER"); + } + parseVersion(); + parseIdSize(); + parseTime(); + } + + private void parseVersion() throws IOException { + String version = BinaryHprof.readMagic(in); + if (version == null) { + throw new MalformedHprofException("Could not find HPROF version"); + } + if (TRACE) { + System.out.println("\tversion=" + version); + } + this.version = version; + } + + private void parseIdSize() throws IOException { + int idSize = in.readInt(); + if (TRACE) { + System.out.println("\tidSize=" + idSize); + } + if (idSize != BinaryHprof.ID_SIZE) { + throw new MalformedHprofException("Unsupported identifier size: " + idSize); + } + } + + private void parseTime() throws IOException { + long time = in.readLong(); + if (TRACE) { + System.out.println("\ttime=" + Long.toHexString(time) + " " + new Date(time)); + } + hprofData.setStartMillis(time); + } + + private void parseRecords() throws IOException { + while (parseRecord()) { + ; + } + } + + /** + * Read and process the next record. Returns true if a + * record was handled, false on EOF. + */ + private boolean parseRecord() throws IOException { + int tagOrEOF = in.read(); + if (tagOrEOF == -1) { + return false; + } + byte tag = (byte) tagOrEOF; + int timeDeltaInMicroseconds = in.readInt(); + int recordLength = in.readInt(); + BinaryHprof.Tag hprofTag = BinaryHprof.Tag.get(tag); + if (TRACE) { + System.out.println("hprofTag=" + hprofTag); + } + if (hprofTag == null) { + skipRecord(hprofTag, recordLength); + return true; + } + String error = hprofTag.checkSize(recordLength); + if (error != null) { + throw new MalformedHprofException(error); + } + switch (hprofTag) { + case CONTROL_SETTINGS: + parseControlSettings(); + return true; + + case STRING_IN_UTF8: + parseStringInUtf8(recordLength); + return true; + + case START_THREAD: + parseStartThread(); + return true; + case END_THREAD: + parseEndThread(); + return true; + + case LOAD_CLASS: + parseLoadClass(); + return true; + case STACK_FRAME: + parseStackFrame(); + return true; + case STACK_TRACE: + parseStackTrace(recordLength); + return true; + + case CPU_SAMPLES: + parseCpuSamples(recordLength); + return true; + + case UNLOAD_CLASS: + case ALLOC_SITES: + case HEAP_SUMMARY: + case HEAP_DUMP: + case HEAP_DUMP_SEGMENT: + case HEAP_DUMP_END: + default: + skipRecord(hprofTag, recordLength); + return true; + } + } + + private void skipRecord(BinaryHprof.Tag hprofTag, long recordLength) throws IOException { + if (TRACE) { + System.out.println("\tskipping recordLength=" + recordLength); + } + long skipped = in.skip(recordLength); + if (skipped != recordLength) { + throw new EOFException("Expected to skip " + recordLength + + " bytes but only skipped " + skipped + " bytes"); + } + } + + private void parseControlSettings() throws IOException { + int flags = in.readInt(); + short depth = in.readShort(); + if (TRACE) { + System.out.println("\tflags=" + Integer.toHexString(flags)); + System.out.println("\tdepth=" + depth); + } + hprofData.setFlags(flags); + hprofData.setDepth(depth); + } + + private void parseStringInUtf8(int recordLength) throws IOException { + int stringId = in.readInt(); + byte[] bytes = new byte[recordLength - BinaryHprof.ID_SIZE]; + readFully(in, bytes); + String string = new String(bytes, "UTF-8"); + if (TRACE) { + System.out.println("\tstring=" + string); + } + String old = idToString.put(stringId, string); + if (old != null) { + throw new MalformedHprofException("Duplicate string id: " + stringId); + } + } + + private static void readFully(InputStream in, byte[] dst) throws IOException { + int offset = 0; + int byteCount = dst.length; + while (byteCount > 0) { + int bytesRead = in.read(dst, offset, byteCount); + if (bytesRead < 0) { + throw new EOFException(); + } + offset += bytesRead; + byteCount -= bytesRead; + } + } + + private void parseLoadClass() throws IOException { + int classId = in.readInt(); + int classObjectId = readId(); + // serial number apparently not a stack trace id. (int vs ID) + // we don't use this field. + int stackTraceSerialNumber = in.readInt(); + String className = readString(); + if (TRACE) { + System.out.println("\tclassId=" + classId); + System.out.println("\tclassObjectId=" + classObjectId); + System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber); + System.out.println("\tclassName=" + className); + } + String old = idToClassName.put(classId, className); + if (old != null) { + throw new MalformedHprofException("Duplicate class id: " + classId); + } + } + + private int readId() throws IOException { + return in.readInt(); + } + + private String readString() throws IOException { + int id = readId(); + if (id == 0) { + return null; + } + String string = idToString.get(id); + if (string == null) { + throw new MalformedHprofException("Unknown string id " + id); + } + return string; + } + + private String readClass() throws IOException { + int id = readId(); + String string = idToClassName.get(id); + if (string == null) { + throw new MalformedHprofException("Unknown class id " + id); + } + return string; + } + + private void parseStartThread() throws IOException { + int threadId = in.readInt(); + int objectId = readId(); + // stack trace where thread was created. + // serial number apparently not a stack trace id. (int vs ID) + // we don't use this field. + int stackTraceSerialNumber = in.readInt(); + String threadName = readString(); + String groupName = readString(); + String parentGroupName = readString(); + if (TRACE) { + System.out.println("\tthreadId=" + threadId); + System.out.println("\tobjectId=" + objectId); + System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber); + System.out.println("\tthreadName=" + threadName); + System.out.println("\tgroupName=" + groupName); + System.out.println("\tparentGroupName=" + parentGroupName); + } + HprofData.ThreadEvent event + = HprofData.ThreadEvent.start(objectId, threadId, + threadName, groupName, parentGroupName); + hprofData.addThreadEvent(event); + } + + private void parseEndThread() throws IOException { + int threadId = in.readInt(); + if (TRACE) { + System.out.println("\tthreadId=" + threadId); + } + HprofData.ThreadEvent event = HprofData.ThreadEvent.end(threadId); + hprofData.addThreadEvent(event); + } + + private void parseStackFrame() throws IOException { + int stackFrameId = readId(); + String methodName = readString(); + String methodSignature = readString(); + String file = readString(); + String className = readClass(); + int line = in.readInt(); + if (TRACE) { + System.out.println("\tstackFrameId=" + stackFrameId); + System.out.println("\tclassName=" + className); + System.out.println("\tmethodName=" + methodName); + System.out.println("\tmethodSignature=" + methodSignature); + System.out.println("\tfile=" + file); + System.out.println("\tline=" + line); + } + StackTraceElement stackFrame = new StackTraceElement(className, methodName, file, line); + StackTraceElement old = idToStackFrame.put(stackFrameId, stackFrame); + if (old != null) { + throw new MalformedHprofException("Duplicate stack frame id: " + stackFrameId); + } + } + + private void parseStackTrace(int recordLength) throws IOException { + int stackTraceId = in.readInt(); + int threadId = in.readInt(); + int frames = in.readInt(); + if (TRACE) { + System.out.println("\tstackTraceId=" + stackTraceId); + System.out.println("\tthreadId=" + threadId); + System.out.println("\tframes=" + frames); + } + int expectedLength = 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE); + if (recordLength != expectedLength) { + throw new MalformedHprofException("Expected stack trace record of size " + + expectedLength + + " based on number of frames but header " + + "specified a length of " + recordLength); + } + StackTraceElement[] stackFrames = new StackTraceElement[frames]; + for (int i = 0; i < frames; i++) { + int stackFrameId = readId(); + StackTraceElement stackFrame = idToStackFrame.get(stackFrameId); + if (TRACE) { + System.out.println("\tstackFrameId=" + stackFrameId); + System.out.println("\tstackFrame=" + stackFrame); + } + if (stackFrame == null) { + throw new MalformedHprofException("Unknown stack frame id " + stackFrameId); + } + stackFrames[i] = stackFrame; + } + + HprofData.StackTrace stackTrace + = new HprofData.StackTrace(stackTraceId, threadId, stackFrames); + if (strict) { + hprofData.addStackTrace(stackTrace, new int[1]); + } else { + // The RI can have duplicate stacks, presumably they + // have a minor race if two samples with the same + // stack are taken around the same time. if we have a + // duplicate, just skip adding it to hprofData, but + // register it locally in idToStackFrame. if it seen + // in CPU_SAMPLES, we will find a StackTrace is equal + // to the first, so they will share a countCell. + int[] countCell = stackTraces.get(stackTrace); + if (countCell == null) { + hprofData.addStackTrace(stackTrace, new int[1]); + } + } + + HprofData.StackTrace old = idToStackTrace.put(stackTraceId, stackTrace); + if (old != null) { + throw new MalformedHprofException("Duplicate stack trace id: " + stackTraceId); + } + + } + + private void parseCpuSamples(int recordLength) throws IOException { + int totalSamples = in.readInt(); + int samplesCount = in.readInt(); + if (TRACE) { + System.out.println("\ttotalSamples=" + totalSamples); + System.out.println("\tsamplesCount=" + samplesCount); + } + int expectedLength = 4 + 4 + (samplesCount * (4 + 4)); + if (recordLength != expectedLength) { + throw new MalformedHprofException("Expected CPU samples record of size " + + expectedLength + + " based on number of samples but header " + + "specified a length of " + recordLength); + } + int total = 0; + for (int i = 0; i < samplesCount; i++) { + int count = in.readInt(); + int stackTraceId = in.readInt(); + if (TRACE) { + System.out.println("\tcount=" + count); + System.out.println("\tstackTraceId=" + stackTraceId); + } + HprofData.StackTrace stackTrace = idToStackTrace.get(stackTraceId); + if (stackTrace == null) { + throw new MalformedHprofException("Unknown stack trace id " + stackTraceId); + } + if (count == 0) { + throw new MalformedHprofException("Zero sample count for stack trace " + + stackTrace); + } + int[] countCell = stackTraces.get(stackTrace); + if (strict) { + if (countCell[0] != 0) { + throw new MalformedHprofException("Setting sample count of stack trace " + + stackTrace + " to " + count + + " found it was already initialized to " + + countCell[0]); + } + } else { + // Coalesce counts from duplicate stack traces. + // For more on this, see comments in parseStackTrace. + count += countCell[0]; + } + countCell[0] = count; + total += count; + } + if (strict && totalSamples != total) { + throw new MalformedHprofException("Expected a total of " + totalSamples + + " samples but saw " + total); + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/BinaryHprofWriter.java b/dalvik/src/main/java/dalvik/system/profiler/BinaryHprofWriter.java new file mode 100644 index 0000000..5c29838 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/BinaryHprofWriter.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * BinaryHprofWriter produces hprof compatible binary output for use + * with third party tools. Such files can be converted to text with + * with {@link HprofBinaryToAscii} or read back in with {@link BinaryHprofReader}. + */ +public final class BinaryHprofWriter { + + private int nextStringId = 1; // id 0 => null + private int nextClassId = 1; + private int nextStackFrameId = 1; + private final Map<String, Integer> stringToId = new HashMap<String, Integer>(); + private final Map<String, Integer> classNameToId = new HashMap<String, Integer>(); + private final Map<StackTraceElement, Integer> stackFrameToId + = new HashMap<StackTraceElement, Integer>(); + + private final HprofData data; + private final DataOutputStream out; + + /** + * Writes the provided data to the specified stream. + */ + public static void write(HprofData data, OutputStream outputStream) throws IOException { + new BinaryHprofWriter(data, outputStream).write(); + } + + private BinaryHprofWriter(HprofData data, OutputStream outputStream) { + this.data = data; + this.out = new DataOutputStream(outputStream); + } + + private void write() throws IOException { + try { + writeHeader(data.getStartMillis()); + + writeControlSettings(data.getFlags(), data.getDepth()); + + for (HprofData.ThreadEvent event : data.getThreadHistory()) { + writeThreadEvent(event); + } + + Set<HprofData.Sample> samples = data.getSamples(); + int total = 0; + for (HprofData.Sample sample : samples) { + total += sample.count; + writeStackTrace(sample.stackTrace); + } + writeCpuSamples(total, samples); + + } finally { + out.flush(); + } + } + + private void writeHeader(long dumpTimeInMilliseconds) throws IOException { + out.writeBytes(BinaryHprof.MAGIC + "1.0.2"); + out.writeByte(0); // null terminated string + out.writeInt(BinaryHprof.ID_SIZE); + out.writeLong(dumpTimeInMilliseconds); + } + + private void writeControlSettings(int flags, int depth) throws IOException { + if (depth > Short.MAX_VALUE) { + throw new IllegalArgumentException("depth too large for binary hprof: " + + depth + " > " + Short.MAX_VALUE); + } + writeRecordHeader(BinaryHprof.Tag.CONTROL_SETTINGS, + 0, + BinaryHprof.Tag.CONTROL_SETTINGS.maximumSize); + out.writeInt(flags); + out.writeShort((short) depth); + } + + private void writeThreadEvent(HprofData.ThreadEvent e) throws IOException { + switch (e.type) { + case START: + writeStartThread(e); + return; + case END: + writeStopThread(e); + return; + } + throw new IllegalStateException(e.type.toString()); + } + + private void writeStartThread(HprofData.ThreadEvent e) throws IOException { + int threadNameId = writeString(e.threadName); + int groupNameId = writeString(e.groupName); + int parentGroupNameId = writeString(e.parentGroupName); + writeRecordHeader(BinaryHprof.Tag.START_THREAD, + 0, + BinaryHprof.Tag.START_THREAD.maximumSize); + out.writeInt(e.threadId); + writeId(e.objectId); + out.writeInt(0); // stack trace where thread was started unavailable + writeId(threadNameId); + writeId(groupNameId); + writeId(parentGroupNameId); + } + + private void writeStopThread(HprofData.ThreadEvent e) throws IOException { + writeRecordHeader(BinaryHprof.Tag.END_THREAD, + 0, + BinaryHprof.Tag.END_THREAD.maximumSize); + out.writeInt(e.threadId); + } + + private void writeRecordHeader(BinaryHprof.Tag hprofTag, + int timeDeltaInMicroseconds, + int recordLength) throws IOException { + String error = hprofTag.checkSize(recordLength); + if (error != null) { + throw new AssertionError(error); + } + out.writeByte(hprofTag.tag); + out.writeInt(timeDeltaInMicroseconds); + out.writeInt(recordLength); + } + + private void writeId(int id) throws IOException { + out.writeInt(id); + } + + /** + * Ensures that a string has been writen to the out and + * returns its ID. The ID of a null string is zero, and + * doesn't actually result in any output. In a string has + * already been written previously, the earlier ID will be + * returned and no output will be written. + */ + private int writeString(String string) throws IOException { + if (string == null) { + return 0; + } + Integer identifier = stringToId.get(string); + if (identifier != null) { + return identifier; + } + + int id = nextStringId++; + stringToId.put(string, id); + + byte[] bytes = string.getBytes("UTF-8"); + writeRecordHeader(BinaryHprof.Tag.STRING_IN_UTF8, + 0, + BinaryHprof.ID_SIZE + bytes.length); + out.writeInt(id); + out.write(bytes, 0, bytes.length); + + return id; + } + + private void writeCpuSamples(int totalSamples, Set<HprofData.Sample> samples) + throws IOException { + int samplesCount = samples.size(); + if (samplesCount == 0) { + return; + } + writeRecordHeader(BinaryHprof.Tag.CPU_SAMPLES, 0, 4 + 4 + (samplesCount * (4 + 4))); + out.writeInt(totalSamples); + out.writeInt(samplesCount); + for (HprofData.Sample sample : samples) { + out.writeInt(sample.count); + out.writeInt(sample.stackTrace.stackTraceId); + } + } + + private void writeStackTrace(HprofData.StackTrace stackTrace) throws IOException { + int frames = stackTrace.stackFrames.length; + int[] stackFrameIds = new int[frames]; + for (int i = 0; i < frames; i++) { + stackFrameIds[i] = writeStackFrame(stackTrace.stackFrames[i]); + } + writeRecordHeader(BinaryHprof.Tag.STACK_TRACE, + 0, + 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE)); + out.writeInt(stackTrace.stackTraceId); + out.writeInt(stackTrace.threadId); + out.writeInt(frames); + for (int stackFrameId : stackFrameIds) { + writeId(stackFrameId); + } + } + + private int writeLoadClass(String className) throws IOException { + Integer identifier = classNameToId.get(className); + if (identifier != null) { + return identifier; + } + int id = nextClassId++; + classNameToId.put(className, id); + + int classNameId = writeString(className); + writeRecordHeader(BinaryHprof.Tag.LOAD_CLASS, + 0, + BinaryHprof.Tag.LOAD_CLASS.maximumSize); + out.writeInt(id); + writeId(0); // class object ID + out.writeInt(0); // stack trace where class was loaded is unavailable + writeId(classNameId); + + return id; + } + + private int writeStackFrame(StackTraceElement stackFrame) throws IOException { + Integer identifier = stackFrameToId.get(stackFrame); + if (identifier != null) { + return identifier; + } + + int id = nextStackFrameId++; + stackFrameToId.put(stackFrame, id); + + int classId = writeLoadClass(stackFrame.getClassName()); + int methodNameId = writeString(stackFrame.getMethodName()); + int sourceId = writeString(stackFrame.getFileName()); + writeRecordHeader(BinaryHprof.Tag.STACK_FRAME, + 0, + BinaryHprof.Tag.STACK_FRAME.maximumSize); + writeId(id); + writeId(methodNameId); + writeId(0); // method signature is unavailable from StackTraceElement + writeId(sourceId); + out.writeInt(classId); + out.writeInt(stackFrame.getLineNumber()); + + return id; + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/DalvikThreadSampler.java b/dalvik/src/main/java/dalvik/system/profiler/DalvikThreadSampler.java new file mode 100644 index 0000000..c20cc32 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/DalvikThreadSampler.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import dalvik.system.VMStack; +import java.util.Arrays; + +class DalvikThreadSampler implements ThreadSampler { + + private int depth; + + /** + * Reusable storage for sampling sized for the specified depth. + */ + private StackTraceElement[][] mutableStackTraceElements; + + @Override public void setDepth(int depth) { + this.depth = depth; + this.mutableStackTraceElements = new StackTraceElement[depth+1][]; + for (int i = 1; i < mutableStackTraceElements.length; i++) { + this.mutableStackTraceElements[i] = new StackTraceElement[i]; + } + } + + @Override public StackTraceElement[] getStackTrace(Thread thread) { + int count = VMStack.fillStackTraceElements(thread, mutableStackTraceElements[depth]); + if (count == 0) { + return null; + } + if (count < depth) { + System.arraycopy(mutableStackTraceElements[depth], 0, + mutableStackTraceElements[count], 0, + count); + } + return mutableStackTraceElements[count]; + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/HprofBinaryToAscii.java b/dalvik/src/main/java/dalvik/system/profiler/HprofBinaryToAscii.java new file mode 100644 index 0000000..46c443d --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/HprofBinaryToAscii.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.BufferedInputStream; +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Run on device with: + * adb shell dalvikvm 'dalvik.system.profiler.HprofBinaryToAscii' + * + * Run on host with: + * java -classpath out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar + */ +public final class HprofBinaryToAscii { + + /** + * Main entry point for HprofBinaryToAscii command line tool + */ + public static void main(String[] args) { + System.exit(convert(args) ? 0 : 1); + } + + /** + * Reads single file from arguments and attempts to read it as + * either a binary hprof file or a version with a text header. + */ + private static boolean convert(String[] args) { + + if (args.length != 1) { + usage("binary hprof file argument expected"); + return false; + } + File file = new File(args[0]); + if (!file.exists()) { + usage("file " + file + " does not exist"); + return false; + } + + if (startsWithMagic(file)) { + HprofData hprofData; + try { + hprofData = readHprof(file); + } catch (IOException e) { + System.out.println("Problem reading binary hprof data from " + + file + ": " + e.getMessage()); + return false; + } + return write(hprofData); + } + + HprofData hprofData; + try { + hprofData = readSnapshot(file); + } catch (IOException e) { + System.out.println("Problem reading snapshot containing binary hprof data from " + + file + ": " + e.getMessage()); + return false; + } + return write(hprofData); + } + + /** + * Probe the start of file to see if it starts with a plausible + * binary hprof magic value. If so, it is returned. On any other + * case including unexpected errors, false is returned. + */ + private static boolean startsWithMagic(File file) { + DataInputStream inputStream = null; + try { + inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); + return BinaryHprof.readMagic(inputStream) != null; + } catch (IOException e) { + return false; + } finally { + closeQuietly(inputStream); + } + } + + /** + * Read and return an HprofData from a vanilla binary hprof file. + */ + private static HprofData readHprof(File file) throws IOException { + InputStream inputStream = null; + try { + inputStream = new BufferedInputStream(new FileInputStream(file)); + return read(inputStream); + } finally { + closeQuietly(inputStream); + } + } + + /** + * Read a file looking for text header terminated by two newlines, + * then proceed to read binary hprof data. + */ + private static HprofData readSnapshot(File file) throws IOException { + InputStream inputStream = null; + try { + inputStream = new BufferedInputStream(new FileInputStream(file)); + int ch; + while ((ch = inputStream.read()) != -1) { + if (ch == '\n' && inputStream.read() == '\n') { + return read(inputStream); + } + } + throw new EOFException("Could not find expected header"); + } finally { + closeQuietly(inputStream); + } + } + + /** + * Read binary hprof data from the provided input stream and + * return the HprofData object. + */ + private static HprofData read(InputStream inputStream) throws IOException { + BinaryHprofReader reader = new BinaryHprofReader(inputStream); + reader.setStrict(false); + reader.read(); + return reader.getHprofData(); + } + + /** + * From IoUtils.closeQuietly but replicated for open source + * version. + */ + private static void closeQuietly(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException ignored) { + } + } + } + + /** + * Write text verion of hprof data to standard output. Returns + * false on error. + */ + private static boolean write(HprofData hprofData) { + try { + AsciiHprofWriter.write(hprofData, System.out); + } catch (IOException e) { + System.out.println("Problem writing ASCII hprof data: " + e.getMessage()); + return false; + } + return true; + } + + /** + * Prints usage error but does not exit. + */ + private static void usage(String error) { + System.out.print("ERROR: "); + System.out.println(error); + System.out.println(); + System.out.println("usage: HprofBinaryToAscii <binary-hprof-file>"); + System.out.println(); + System.out.println("Reads a binary hprof file and print it in ASCII format"); + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/HprofData.java b/dalvik/src/main/java/dalvik/system/profiler/HprofData.java new file mode 100644 index 0000000..7b44bb9 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/HprofData.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Map; +import java.util.Set; + +/** + * Represents sampling profiler data. Can be converted to ASCII or + * binary hprof-style output using {@link AsciiHprofWriter} or + * {@link BinaryHprofWriter}. + * <p> + * The data includes: + * <ul> + * <li>the start time of the last sampling period + * <li>the history of thread start and end events + * <li>stack traces with frequency counts + * <ul> + */ +public final class HprofData { + + public static enum ThreadEventType { START, END }; + + /** + * ThreadEvent represents thread creation and death events for + * reporting. It provides a record of the thread and thread group + * names for tying samples back to their source thread. + */ + public static final class ThreadEvent { + + public final ThreadEventType type; + public final int objectId; + public final int threadId; + public final String threadName; + public final String groupName; + public final String parentGroupName; + + public static ThreadEvent start(int objectId, int threadId, String threadName, + String groupName, String parentGroupName) { + return new ThreadEvent(ThreadEventType.START, objectId, threadId, + threadName, groupName, parentGroupName); + } + + public static ThreadEvent end(int threadId) { + return new ThreadEvent(ThreadEventType.END, threadId); + } + + private ThreadEvent(ThreadEventType type, int objectId, int threadId, + String threadName, String groupName, String parentGroupName) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + this.type = ThreadEventType.START; + this.objectId = objectId; + this.threadId = threadId; + this.threadName = threadName; + this.groupName = groupName; + this.parentGroupName = parentGroupName; + } + + private ThreadEvent(ThreadEventType type, int threadId) { + this.type = ThreadEventType.END; + this.objectId = -1; + this.threadId = threadId; + this.threadName = null; + this.groupName = null; + this.parentGroupName = null; + } + + @Override public int hashCode() { + int result = 17; + result = 31 * result + objectId; + result = 31 * result + threadId; + result = 31 * result + hashCode(threadName); + result = 31 * result + hashCode(groupName); + result = 31 * result + hashCode(parentGroupName); + return result; + } + + private static int hashCode(Object o) { + return (o == null) ? 0 : o.hashCode(); + } + + @Override public boolean equals(Object o) { + if (!(o instanceof ThreadEvent)) { + return false; + } + ThreadEvent event = (ThreadEvent) o; + return (this.type == event.type + && this.objectId == event.objectId + && this.threadId == event.threadId + && equal(this.threadName, event.threadName) + && equal(this.groupName, event.groupName) + && equal(this.parentGroupName, event.parentGroupName)); + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + @Override public String toString() { + switch (type) { + case START: + return String.format( + "THREAD START (obj=%d, id = %d, name=\"%s\", group=\"%s\")", + objectId, threadId, threadName, groupName); + case END: + return String.format("THREAD END (id = %d)", threadId); + } + throw new IllegalStateException(type.toString()); + } + } + + /** + * A unique stack trace for a specific thread. + */ + public static final class StackTrace { + + public final int stackTraceId; + int threadId; + StackTraceElement[] stackFrames; + + StackTrace() { + this.stackTraceId = -1; + } + + public StackTrace(int stackTraceId, int threadId, StackTraceElement[] stackFrames) { + if (stackFrames == null) { + throw new NullPointerException("stackFrames == null"); + } + this.stackTraceId = stackTraceId; + this.threadId = threadId; + this.stackFrames = stackFrames; + } + + public int getThreadId() { + return threadId; + } + + public StackTraceElement[] getStackFrames() { + return stackFrames; + } + + @Override public int hashCode() { + int result = 17; + result = 31 * result + threadId; + result = 31 * result + Arrays.hashCode(stackFrames); + return result; + } + + @Override public boolean equals(Object o) { + if (!(o instanceof StackTrace)) { + return false; + } + StackTrace s = (StackTrace) o; + return threadId == s.threadId && Arrays.equals(stackFrames, s.stackFrames); + } + + @Override public String toString() { + StringBuilder frames = new StringBuilder(); + if (stackFrames.length > 0) { + frames.append('\n'); + for (StackTraceElement stackFrame : stackFrames) { + frames.append("\t at "); + frames.append(stackFrame); + frames.append('\n'); + } + } else { + frames.append("<empty>"); + } + return "StackTrace[stackTraceId=" + stackTraceId + + ", threadId=" + threadId + + ", frames=" + frames + "]"; + + } + } + + /** + * A read only container combining a stack trace with its frequency. + */ + public static final class Sample { + + public final StackTrace stackTrace; + public final int count; + + private Sample(StackTrace stackTrace, int count) { + if (stackTrace == null) { + throw new NullPointerException("stackTrace == null"); + } + if (count < 0) { + throw new IllegalArgumentException("count < 0:" + count); + } + this.stackTrace = stackTrace; + this.count = count; + } + + @Override public int hashCode() { + int result = 17; + result = 31 * result + stackTrace.hashCode(); + result = 31 * result + count; + return result; + } + + @Override public boolean equals(Object o) { + if (!(o instanceof Sample)) { + return false; + } + Sample s = (Sample) o; + return count == s.count && stackTrace.equals(s.stackTrace); + } + + @Override public String toString() { + return "Sample[count=" + count + " " + stackTrace + "]"; + } + + } + + /** + * Start of last sampling period. + */ + private long startMillis; + + /** + * CONTROL_SETTINGS flags + */ + private int flags; + + /** + * stack sampling depth + */ + private int depth; + + /** + * List of thread creation and death events. + */ + private final List<ThreadEvent> threadHistory = new ArrayList<ThreadEvent>(); + + /** + * Map of thread id to a start ThreadEvent + */ + private final Map<Integer, ThreadEvent> threadIdToThreadEvent + = new HashMap<Integer, ThreadEvent>(); + + /** + * Map of stack traces to a mutable sample count. The map is + * provided by the creator of the HprofData so only have + * mutable access to the int[] cells that contain the sample + * count. Only an unmodifiable iterator view is available to + * users of the HprofData. + */ + private final Map<HprofData.StackTrace, int[]> stackTraces; + + public HprofData(Map<StackTrace, int[]> stackTraces) { + if (stackTraces == null) { + throw new NullPointerException("stackTraces == null"); + } + this.stackTraces = stackTraces; + } + + /** + * The start time in milliseconds of the last profiling period. + */ + public long getStartMillis() { + return startMillis; + } + + /** + * Set the time for the start of the current sampling period. + */ + public void setStartMillis(long startMillis) { + this.startMillis = startMillis; + } + + /** + * Get the {@link BinaryHprof.ControlSettings} flags + */ + public int getFlags() { + return flags; + } + + /** + * Set the {@link BinaryHprof.ControlSettings} flags + */ + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * Get the stack sampling depth + */ + public int getDepth() { + return depth; + } + + /** + * Set the stack sampling depth + */ + public void setDepth(int depth) { + this.depth = depth; + } + + /** + * Return an unmodifiable history of start and end thread events. + */ + public List<ThreadEvent> getThreadHistory() { + return Collections.unmodifiableList(threadHistory); + } + + /** + * Return a new set containing the current sample data. + */ + public Set<Sample> getSamples() { + Set<Sample> samples = new HashSet<Sample>(stackTraces.size()); + for (Entry<StackTrace, int[]> e : stackTraces.entrySet()) { + StackTrace stackTrace = e.getKey(); + int countCell[] = e.getValue(); + int count = countCell[0]; + Sample sample = new Sample(stackTrace, count); + samples.add(sample); + } + return samples; + } + + /** + * Record an event in the thread history. + */ + public void addThreadEvent(ThreadEvent event) { + if (event == null) { + throw new NullPointerException("event == null"); + } + ThreadEvent old = threadIdToThreadEvent.put(event.threadId, event); + switch (event.type) { + case START: + if (old != null) { + throw new IllegalArgumentException("ThreadEvent already registered for id " + + event.threadId); + } + break; + case END: + // Do not assert that the END_THREAD matches a + // START_THREAD unless in strict mode. While thhis + // hold true in the binary hprof BinaryHprofWriter + // produces, it is not true of hprof files created + // by the RI. However, if there is an event + // already registed for a thread id, it should be + // the matching start, not a duplicate end. + if (old != null && old.type == ThreadEventType.END) { + throw new IllegalArgumentException("Duplicate ThreadEvent.end for id " + + event.threadId); + } + break; + } + threadHistory.add(event); + } + + /** + * Record an stack trace and an associated int[] cell of + * sample cound for the stack trace. The caller is allowed + * retain a pointer to the cell to update the count. The + * SamplingProfiler intentionally does not present a mutable + * view of the count. + */ + public void addStackTrace(StackTrace stackTrace, int[] countCell) { + if (!threadIdToThreadEvent.containsKey(stackTrace.threadId)) { + throw new IllegalArgumentException("Unknown thread id " + stackTrace.threadId); + } + int[] old = stackTraces.put(stackTrace, countCell); + if (old != null) { + throw new IllegalArgumentException("StackTrace already registered for id " + + stackTrace.stackTraceId + ":\n" + stackTrace); + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/MalformedHprofException.java b/dalvik/src/main/java/dalvik/system/profiler/MalformedHprofException.java new file mode 100644 index 0000000..b4097b2 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/MalformedHprofException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.IOException; + +public final class MalformedHprofException extends IOException { + + private static final long serialVersionUID = 8558990237047894213L; + + MalformedHprofException(String message) { + super(message); + } + MalformedHprofException(String message, Throwable cause) { + super(message, cause); + } + MalformedHprofException(Throwable cause) { + super(cause); + } +} + diff --git a/dalvik/src/main/java/dalvik/system/profiler/PortableThreadSampler.java b/dalvik/src/main/java/dalvik/system/profiler/PortableThreadSampler.java new file mode 100644 index 0000000..96e0b27 --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/PortableThreadSampler.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.util.Arrays; + +/** + * ThreadSampler implementation that only uses Thread.getStackTrace() + * and therefore is portable. + */ +class PortableThreadSampler implements ThreadSampler { + + private int depth; + + @Override public void setDepth(int depth) { + this.depth = depth; + } + + @Override public StackTraceElement[] getStackTrace(Thread thread) { + StackTraceElement[] stackFrames = thread.getStackTrace(); + if (stackFrames.length == 0) { + return null; + } + if (stackFrames.length > depth) { + stackFrames = Arrays.copyOfRange(stackFrames, 0, depth); + } + return stackFrames; + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java b/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java new file mode 100644 index 0000000..744977c --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +/** + * A sampling profiler. It currently is implemented without any + * virtual machine support, relying solely on {@code + * Thread.getStackTrace} to collect samples. As such, the overhead is + * higher than a native approach and it does not provide insight into + * where time is spent within native code, but it can still provide + * useful insight into where a program is spending time. + * + * <h3>Usage Example</h3> + * + * The following example shows how to use the {@code + * SamplingProfiler}. It samples the current thread's stack to a depth + * of 12 stack frame elements over two different measurement periods + * with samples taken every 100 milliseconds. In then prints the + * results in hprof format to the standard output. + * + * <pre> {@code + * ThreadSet threadSet = SamplingProfiler.newArrayThreadSet(Thread.currentThread()); + * SamplingProfiler profiler = new SamplingProfiler(12, threadSet); + * profiler.start(100); + * // period of measurement + * profiler.stop(); + * // period of non-measurement + * profiler.start(100); + * // another period of measurement + * profiler.stop(); + * profiler.shutdown(); + * AsciiHprofWriter.write(profiler.getHprofData(), System.out); + * }</pre> + */ +public final class SamplingProfiler { + + /** + * Map of stack traces to a mutable sample count. + */ + private final Map<HprofData.StackTrace, int[]> stackTraces + = new HashMap<HprofData.StackTrace, int[]>(); + + /** + * Data collected by the sampling profiler + */ + private final HprofData hprofData = new HprofData(stackTraces); + + /** + * Timer that is used for the lifetime of the profiler + */ + private final Timer timer = new Timer("SamplingProfiler", true); + + /** + * A sampler is created every time profiling starts and cleared + * every time profiling stops because once a {@code TimerTask} is + * canceled it cannot be reused. + */ + private Sampler sampler; + + /** + * The maximum number of {@code StackTraceElements} to retain in + * each stack. + */ + private final int depth; + + /** + * The {@code ThreadSet} that identifies which threads to sample. + */ + private final ThreadSet threadSet; + + /* + * Real hprof output examples don't start the thread and trace + * identifiers at one but seem to start at these arbitrary + * constants. It certainly seems useful to have relatively unique + * identifiers when manual searching hprof output. + */ + private int nextThreadId = 200001; + private int nextStackTraceId = 300001; + private int nextObjectId = 1; + + /** + * The threads currently known to the profiler for detecting + * thread start and end events. + */ + private Thread[] currentThreads = new Thread[0]; + + /** + * Map of currently active threads to their identifiers. When + * threads disappear they are removed and only referenced by their + * identifiers to prevent retaining garbage threads. + */ + private final Map<Thread, Integer> threadIds = new HashMap<Thread, Integer>(); + + /** + * Mutable {@code StackTrace} that is used for probing the {@link + * #stackTraces stackTraces} map without allocating a {@code + * StackTrace}. If {@link #addStackTrace addStackTrace} needs to + * be thread safe, have a single mutable instance would need to be + * reconsidered. + */ + private final HprofData.StackTrace mutableStackTrace = new HprofData.StackTrace(); + + /** + * The {@code ThreadSampler} is used to produce a {@code + * StackTraceElement} array for a given thread. The array is + * expected to be {@link #depth depth} or less in length. + */ + private final ThreadSampler threadSampler; + + /** + * Create a sampling profiler that collects stacks with the + * specified depth from the threads specified by the specified + * thread collector. + * + * @param depth The maximum stack depth to retain for each sample + * similar to the hprof option of the same name. Any stack deeper + * than this will be truncated to this depth. A good starting + * value is 4 although it is not uncommon to need to raise this to + * get enough context to understand program behavior. While + * programs with extensive recursion may require a high value for + * depth, simply passing in a value for Integer.MAX_VALUE is not + * advised because of the significant memory need to retain such + * stacks and runtime overhead to compare stacks. + * + * @param threadSet The thread set specifies which threads to + * sample. In a general purpose program, all threads typically + * should be sample with a ThreadSet such as provided by {@link + * #newThreadGroupThreadSet newThreadGroupThreadSet}. For a + * benchmark a fixed set such as provided by {@link + * #newArrayThreadSet newArrayThreadSet} can reduce the overhead + * of profiling. + */ + public SamplingProfiler(int depth, ThreadSet threadSet) { + this.depth = depth; + this.threadSet = threadSet; + this.threadSampler = findDefaultThreadSampler(); + threadSampler.setDepth(depth); + hprofData.setFlags(BinaryHprof.ControlSettings.CPU_SAMPLING.bitmask); + hprofData.setDepth(depth); + } + + private static ThreadSampler findDefaultThreadSampler() { + if ("Dalvik Core Library".equals(System.getProperty("java.specification.name"))) { + String className = "dalvik.system.profiler.DalvikThreadSampler"; + try { + return (ThreadSampler) Class.forName(className).newInstance(); + } catch (Exception e) { + System.out.println("Problem creating " + className + ": " + e); + } + } + return new PortableThreadSampler(); + } + + /** + * A ThreadSet specifies the set of threads to sample. + */ + public static interface ThreadSet { + /** + * Returns an array containing the threads to be sampled. The + * array may be longer than the number of threads to be + * sampled, in which case the extra elements must be null. + */ + public Thread[] threads(); + } + + /** + * Returns a ThreadSet for a fixed set of threads that will not + * vary at runtime. This has less overhead than a dynamically + * calculated set, such as {@link #newThreadGroupThreadSet}, which has + * to enumerate the threads each time profiler wants to collect + * samples. + */ + public static ThreadSet newArrayThreadSet(Thread... threads) { + return new ArrayThreadSet(threads); + } + + /** + * An ArrayThreadSet samples a fixed set of threads that does not + * vary over the life of the profiler. + */ + private static class ArrayThreadSet implements ThreadSet { + private final Thread[] threads; + public ArrayThreadSet(Thread... threads) { + if (threads == null) { + throw new NullPointerException("threads == null"); + } + this.threads = threads; + } + public Thread[] threads() { + return threads; + } + } + + /** + * Returns a ThreadSet that is dynamically computed based on the + * threads found in the specified ThreadGroup and that + * ThreadGroup's children. + */ + public static ThreadSet newThreadGroupThreadSet(ThreadGroup threadGroup) { + return new ThreadGroupThreadSet(threadGroup); + } + + /** + * An ThreadGroupThreadSet sample the threads from the specified + * ThreadGroup and the ThreadGroup's children + */ + private static class ThreadGroupThreadSet implements ThreadSet { + private final ThreadGroup threadGroup; + private Thread[] threads; + private int lastThread; + + public ThreadGroupThreadSet(ThreadGroup threadGroup) { + if (threadGroup == null) { + throw new NullPointerException("threadGroup == null"); + } + this.threadGroup = threadGroup; + resize(); + } + + private void resize() { + int count = threadGroup.activeCount(); + // we can only tell if we had enough room for all active + // threads if we actually are larger than the the number of + // active threads. making it larger also leaves us room to + // tolerate additional threads without resizing. + threads = new Thread[count*2]; + lastThread = 0; + } + + public Thread[] threads() { + int threadCount; + while (true) { + threadCount = threadGroup.enumerate(threads); + if (threadCount == threads.length) { + resize(); + } else { + break; + } + } + if (threadCount < lastThread) { + // avoid retaining pointers to threads that have ended + Arrays.fill(threads, threadCount, lastThread, null); + } + lastThread = threadCount; + return threads; + } + } + + /** + * Starts profiler sampling at the specified rate. + * + * @param interval The number of milliseconds between samples + */ + public void start(int interval) { + if (interval < 1) { + throw new IllegalArgumentException("interval < 1"); + } + if (sampler != null) { + throw new IllegalStateException("profiling already started"); + } + sampler = new Sampler(); + hprofData.setStartMillis(System.currentTimeMillis()); + timer.scheduleAtFixedRate(sampler, 0, interval); + } + + /** + * Stops profiler sampling. It can be restarted with {@link + * #start(int)} to continue sampling. + */ + public void stop() { + if (sampler == null) { + return; + } + synchronized(sampler) { + sampler.stop = true; + while (!sampler.stopped) { + try { + sampler.wait(); + } catch (InterruptedException ignored) { + } + } + } + sampler = null; + } + + /** + * Shuts down profiling after which it can not be restarted. It is + * important to shut down profiling when done to free resources + * used by the profiler. Shutting down the profiler also stops the + * profiling if that has not already been done. + */ + public void shutdown() { + stop(); + timer.cancel(); + } + + /** + * Returns the hprof data accumulated by the profiler since it was + * created. The profiler needs to be stopped, but not necessarily + * shut down, in order to access the data. If the profiler is + * restarted, there is no thread safe way to access the data. + */ + public HprofData getHprofData() { + if (sampler != null) { + throw new IllegalStateException("cannot access hprof data while sampling"); + } + return hprofData; + } + + /** + * The Sampler does the real work of the profiler. + * + * At every sample time, it asks the thread set for the set + * of threads to sample. It maintains a history of thread creation + * and death events based on changes observed to the threads + * returned by the {@code ThreadSet}. + * + * For each thread to be sampled, a stack is collected and used to + * update the set of collected samples. Stacks are truncated to a + * maximum depth. There is no way to tell if a stack has been truncated. + */ + private class Sampler extends TimerTask { + + private boolean stop; + private boolean stopped; + + private Thread timerThread; + + public void run() { + synchronized(this) { + if (stop) { + cancel(); + stopped = true; + notifyAll(); + return; + } + } + + if (timerThread == null) { + timerThread = Thread.currentThread(); + } + + // process thread creation and death first so that we + // assign thread ids to any new threads before allocating + // new stacks for them + Thread[] newThreads = threadSet.threads(); + if (!Arrays.equals(currentThreads, newThreads)) { + updateThreadHistory(currentThreads, newThreads); + currentThreads = newThreads.clone(); + } + + for (Thread thread : currentThreads) { + if (thread == null) { + break; + } + if (thread == timerThread) { + continue; + } + + StackTraceElement[] stackFrames = threadSampler.getStackTrace(thread); + if (stackFrames == null) { + continue; + } + recordStackTrace(thread, stackFrames); + } + } + + /** + * Record a new stack trace. The thread should have been + * previously registered with addStartThread. + */ + private void recordStackTrace(Thread thread, StackTraceElement[] stackFrames) { + Integer threadId = threadIds.get(thread); + if (threadId == null) { + throw new IllegalArgumentException("Unknown thread " + thread); + } + mutableStackTrace.threadId = threadId; + mutableStackTrace.stackFrames = stackFrames; + + int[] countCell = stackTraces.get(mutableStackTrace); + if (countCell == null) { + countCell = new int[1]; + // cloned because the ThreadSampler may reuse the array + StackTraceElement[] stackFramesCopy = stackFrames.clone(); + HprofData.StackTrace stackTrace + = new HprofData.StackTrace(nextStackTraceId++, threadId, stackFramesCopy); + hprofData.addStackTrace(stackTrace, countCell); + } + countCell[0]++; + } + + private void updateThreadHistory(Thread[] oldThreads, Thread[] newThreads) { + // thread start/stop shouldn't happen too often and + // these aren't too big, so hopefully this approach + // won't be too slow... + Set<Thread> n = new HashSet<Thread>(Arrays.asList(newThreads)); + Set<Thread> o = new HashSet<Thread>(Arrays.asList(oldThreads)); + + // added = new-old + Set<Thread> added = new HashSet<Thread>(n); + added.removeAll(o); + + // removed = old-new + Set<Thread> removed = new HashSet<Thread>(o); + removed.removeAll(n); + + for (Thread thread : added) { + if (thread == null) { + continue; + } + if (thread == timerThread) { + continue; + } + addStartThread(thread); + } + for (Thread thread : removed) { + if (thread == null) { + continue; + } + if (thread == timerThread) { + continue; + } + addEndThread(thread); + } + } + + /** + * Record that a newly noticed thread. + */ + private void addStartThread(Thread thread) { + if (thread == null) { + throw new NullPointerException("thread == null"); + } + int threadId = nextThreadId++; + Integer old = threadIds.put(thread, threadId); + if (old != null) { + throw new IllegalArgumentException("Thread already registered as " + old); + } + + String threadName = thread.getName(); + // group will become null when thread is terminated + ThreadGroup group = thread.getThreadGroup(); + String groupName = group == null ? null : group.getName(); + ThreadGroup parentGroup = group == null ? null : group.getParent(); + String parentGroupName = parentGroup == null ? null : parentGroup.getName(); + + HprofData.ThreadEvent event + = HprofData.ThreadEvent.start(nextObjectId++, threadId, + threadName, groupName, parentGroupName); + hprofData.addThreadEvent(event); + } + + /** + * Record that a thread has disappeared. + */ + private void addEndThread(Thread thread) { + if (thread == null) { + throw new NullPointerException("thread == null"); + } + Integer threadId = threadIds.remove(thread); + if (threadId == null) { + throw new IllegalArgumentException("Unknown thread " + thread); + } + HprofData.ThreadEvent event = HprofData.ThreadEvent.end(threadId); + hprofData.addThreadEvent(event); + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/profiler/ThreadSampler.java b/dalvik/src/main/java/dalvik/system/profiler/ThreadSampler.java new file mode 100644 index 0000000..104b16d --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/profiler/ThreadSampler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 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 dalvik.system.profiler; + +import java.io.IOException; + +/** + * The {@code ThreadSampler} interfaces allows a profiler to choose + * between portable and VM specific implementations of thread + * sampling. + */ +public interface ThreadSampler { + + /** + * Used to specify the maximum stack depth to collect. + */ + public void setDepth(int depth); + + /** + * Return a stack trace for the current thread limited by the + * maximum depth specified by {@link #setDepth setDepth}. May + * return null if no sample is availble for the thread, which may + * happen in cases such as thread termination. The resulting array + * should be copied before the next call to {@code getStackTrace} + * if the caller wants to use the results, since the {@code + * ThreadSampler} may reuse the array. Note that the elements + * themselves are immutable and do not need to be copied. + */ + public StackTraceElement[] getStackTrace(Thread thread); +} diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java b/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java new file mode 100644 index 0000000..7b46d2e --- /dev/null +++ b/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 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 org.apache.harmony.dalvik; + +/** + * Methods used to test calling into native code. The methods in this + * class are all effectively no-ops and may be used to test the mechanisms + * and performance of calling native methods. + */ +public final class NativeTestTarget { + /** + * This class is uninstantiable. + */ + private NativeTestTarget() { + // This space intentionally left blank. + } + + /** + * This is an empty native static method with no args, hooked up using + * JNI. + */ + public static native void emptyJniStaticMethod0(); + + /** + * This is an empty native static method with six args, hooked up using + * JNI. + */ + public static native void emptyJniStaticMethod6(int a, int b, int c, + int d, int e, int f); + + /** + * This is an empty native static method with six args, hooked up + * using JNI. These have more complex args to show the cost of + * parsing the signature. All six values should be null + * references. + */ + public static native void emptyJniStaticMethod6L(String a, String[] b, + int[][] c, Object d, Object[] e, Object[][][][] f); + + /** + * This method is intended to be "inlined" by the virtual machine + * (e.g., given special treatment as an intrinsic). + */ + public static void emptyInlineMethod() { + // This space intentionally left blank. + } + + /** + * This method is intended to be defined in native code and hooked + * up using the virtual machine's special fast-path native linkage + * (as opposed to being hooked up using JNI). + */ + public static native void emptyInternalStaticMethod(); +} diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/Chunk.java b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/Chunk.java new file mode 100644 index 0000000..9b98893 --- /dev/null +++ b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/Chunk.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 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 org.apache.harmony.dalvik.ddmc; + +import java.nio.ByteBuffer; + +/** + * A chunk of DDM data. This is really just meant to hold a few pieces + * of data together. + * + * The "offset" and "length" fields are present so handlers can over-allocate + * or share byte buffers. + */ +public class Chunk { + + /* + * Public members. Do not rename without updating the VM. + */ + public int type; // chunk type + public byte[] data; // chunk data + public int offset, length; // position within "data" + + /** + * Blank constructor. Fill in your own fields. + */ + public Chunk() {} + + /** + * Constructor with all fields. + */ + public Chunk(int type, byte[] data, int offset, int length) { + this.type = type; + this.data = data; + this.offset = offset; + this.length = length; + } + + /** + * Construct from a ByteBuffer. The chunk is assumed to start at + * offset 0 and continue to the current position. + */ + public Chunk(int type, ByteBuffer buf) { + this.type = type; + + this.data = buf.array(); + this.offset = buf.arrayOffset(); + this.length = buf.position(); + } +} + diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/ChunkHandler.java b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/ChunkHandler.java new file mode 100644 index 0000000..5d0073b --- /dev/null +++ b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/ChunkHandler.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 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 org.apache.harmony.dalvik.ddmc; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Handle a chunk of data sent from a DDM server. + * + * To handle a chunk type, sub-class ChunkHandler and register your class + * with DdmServer. + */ +public abstract class ChunkHandler { + + public static final ByteOrder CHUNK_ORDER = ByteOrder.BIG_ENDIAN; + + public static final int CHUNK_FAIL = type("FAIL"); + + + public ChunkHandler() {} + + /** + * Called when the DDM server connects. The handler is allowed to + * send messages to the server. + */ + public abstract void connected(); + + /** + * Called when the DDM server disconnects. Can be used to disable + * periodic transmissions or clean up saved state. + */ + public abstract void disconnected(); + + /** + * Handle a single chunk of data. "request" includes the type and + * the chunk payload. + * + * Returns a response in a Chunk. + */ + public abstract Chunk handleChunk(Chunk request); + + /** + * Create a FAIL chunk. The "handleChunk" methods can use this to + * return an error message when they are not able to process a chunk. + */ + public static Chunk createFailChunk(int errorCode, String msg) { + if (msg == null) + msg = ""; + + ByteBuffer out = ByteBuffer.allocate(8 + msg.length() * 2); + out.order(ChunkHandler.CHUNK_ORDER); + out.putInt(errorCode); + out.putInt(msg.length()); + putString(out, msg); + + return new Chunk(CHUNK_FAIL, out); + } + + /** + * Utility function to wrap a ByteBuffer around a Chunk. + */ + public static ByteBuffer wrapChunk(Chunk request) { + ByteBuffer in; + + in = ByteBuffer.wrap(request.data, request.offset, request.length); + in.order(CHUNK_ORDER); + return in; + } + + + /** + * Utility function to copy a String out of a ByteBuffer. + * + * This is here because multiple chunk handlers can make use of it, + * and there's nowhere better to put it. + */ + public static String getString(ByteBuffer buf, int len) { + char[] data = new char[len]; + for (int i = 0; i < len; i++) + data[i] = buf.getChar(); + return new String(data); + } + + /** + * Utility function to copy a String into a ByteBuffer. + */ + public static void putString(ByteBuffer buf, String str) { + int len = str.length(); + for (int i = 0; i < len; i++) + buf.putChar(str.charAt(i)); + } + + /** + * Convert a 4-character string to a 32-bit type. + */ + public static int type(String typeName) { + if (typeName.length() != 4) { + throw new IllegalArgumentException("Bad type name: " + typeName); + } + int result = 0; + for (int i = 0; i < 4; ++i) { + result = ((result << 8) | (typeName.charAt(i) & 0xff)); + } + return result; + } + + /** + * Convert an integer type to a 4-character string. + */ + public static String name(int type) + { + char[] ascii = new char[4]; + + ascii[0] = (char) ((type >> 24) & 0xff); + ascii[1] = (char) ((type >> 16) & 0xff); + ascii[2] = (char) ((type >> 8) & 0xff); + ascii[3] = (char) (type & 0xff); + + return new String(ascii); + } + +} diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java new file mode 100644 index 0000000..7717fd9 --- /dev/null +++ b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 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 org.apache.harmony.dalvik.ddmc; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + + +/** + * This represents our connection to the DDM Server. + */ +public class DdmServer { + + public static final int CLIENT_PROTOCOL_VERSION = 1; + + private static HashMap<Integer,ChunkHandler> mHandlerMap = + new HashMap<Integer,ChunkHandler>(); + + private static final int CONNECTED = 1; + private static final int DISCONNECTED = 2; + + private static volatile boolean mRegistrationComplete = false; + private static boolean mRegistrationTimedOut = false; + + + /** + * Don't instantiate; all members and methods are static. + */ + private DdmServer() {} + + /** + * Register an instance of the ChunkHandler class to handle a specific + * chunk type. + * + * Throws an exception if the type already has a handler registered. + */ + public static void registerHandler(int type, ChunkHandler handler) { + if (handler == null) { + throw new NullPointerException("handler == null"); + } + synchronized (mHandlerMap) { + if (mHandlerMap.get(type) != null) + throw new RuntimeException("type " + Integer.toHexString(type) + + " already registered"); + + mHandlerMap.put(type, handler); + } + } + + /** + * Unregister the existing handler for the specified type. + * + * Returns the old handler. + */ + public static ChunkHandler unregisterHandler(int type) { + synchronized (mHandlerMap) { + return mHandlerMap.remove(type); + } + } + + /** + * The application must call here after it finishes registering + * handlers. + */ + public static void registrationComplete() { + // sync on mHandlerMap because it's convenient and makes a kind of + // sense + synchronized (mHandlerMap) { + mRegistrationComplete = true; + mHandlerMap.notifyAll(); + } + } + + /** + * Send a chunk of data to the DDM server. This takes the form of a + * JDWP "event", which does not elicit a response from the server. + * + * Use this for "unsolicited" chunks. + */ + public static void sendChunk(Chunk chunk) { + nativeSendChunk(chunk.type, chunk.data, chunk.offset, chunk.length); + } + + /* send a chunk to the DDM server */ + native private static void nativeSendChunk(int type, byte[] data, + int offset, int length); + + /* + * Called by the VM when the DDM server connects or disconnects. + */ + private static void broadcast(int event) + { + synchronized (mHandlerMap) { + Collection values = mHandlerMap.values(); + Iterator iter = values.iterator(); + + while (iter.hasNext()) { + ChunkHandler handler = (ChunkHandler) iter.next(); + switch (event) { + case CONNECTED: + handler.connected(); + break; + case DISCONNECTED: + handler.disconnected(); + break; + default: + throw new UnsupportedOperationException(); + } + } + } + } + + /* + * This is called by the VM when a chunk arrives. + * + * For a DDM-aware application, we want to wait until the app has had + * a chance to register all of its chunk handlers. Otherwise, we'll + * end up dropping early-arriving packets on the floor. + * + * For a non-DDM-aware application, we'll end up waiting here forever + * if DDMS happens to connect. It's hard to know for sure that + * registration isn't going to happen, so we settle for a timeout. + */ + private static Chunk dispatch(int type, byte[] data, int offset, int length) + { + ChunkHandler handler; + + synchronized (mHandlerMap) { + /* + * If registration hasn't completed, and we haven't timed out + * waiting for it, wait a bit. + */ + while (!mRegistrationComplete && !mRegistrationTimedOut) { + //System.out.println("dispatch() waiting for reg"); + try { + mHandlerMap.wait(1000); // 1.0 sec + } catch (InterruptedException ie) { + continue; + } + + if (!mRegistrationComplete) { + /* timed out, don't wait again */ + mRegistrationTimedOut = true; + } + } + + handler = mHandlerMap.get(type); + } + //System.out.println(" dispatch cont"); + + if (handler == null) { + return null; + } + + Chunk chunk = new Chunk(type, data, offset, length); + return handler.handleChunk(chunk); + } +} diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmVmInternal.java b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmVmInternal.java new file mode 100644 index 0000000..01293b0 --- /dev/null +++ b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmVmInternal.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2007 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 org.apache.harmony.dalvik.ddmc; + +/** + * Declarations for some VM-internal DDM stuff. + */ +public class DdmVmInternal { + + /* do not instantiate */ + private DdmVmInternal() {} + + /** + * Enable thread notification. + * + * This is built into the VM, since that's where threads get managed. + */ + native public static void threadNotify(boolean enable); + + /** + * Enable heap info updates. + * + * This is built into the VM, since that's where the heap is managed. + * + * @param when when to send the next HPIF chunk + * @return true on success. false if 'when' is bad or if there was + * an internal error. + */ + native public static boolean heapInfoNotify(int when); + + /** + * Enable heap segment updates for the java (isNative == false) or + * native (isNative == true) heap. + * + * This is built into the VM, since that's where the heap is managed. + */ + native public static boolean heapSegmentNotify(int when, int what, + boolean isNative); + + /** + * Get status info for all threads. This is for the THST chunk. + * + * Returns a byte array with the THST data, or null if something + * went wrong. + */ + native public static byte[] getThreadStats(); + + /** + * Get a stack trace for the specified thread ID. The ID can be found + * in the data from getThreadStats. + */ + native public static StackTraceElement[] getStackTraceById(int threadId); + + /** + * Enable or disable "recent allocation" tracking. + */ + native public static void enableRecentAllocations(boolean enable); + + /* + * Return a boolean indicating whether or not the "recent allocation" + * feature is currently enabled. + */ + native public static boolean getRecentAllocationStatus(); + + /** + * Fill a buffer with data on recent heap allocations. + */ + native public static byte[] getRecentAllocations(); +} + diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/README.txt b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/README.txt new file mode 100644 index 0000000..99def80 --- /dev/null +++ b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/README.txt @@ -0,0 +1 @@ +These classes are tied to the VM, e.g. the VM may invoke some of the methods. |