Repository: jbachorik/btrace Branch: develop Commit: c354f12be41a Files: 1026 Total size: 4.0 MB Directory structure: gitextract_vcnq_xtn/ ├── .github/ │ ├── FUNDING.yml │ ├── copilot-instructions.md │ └── workflows/ │ ├── README.md │ ├── codeql-analysis.yml │ ├── continuous.yml │ ├── release.yml │ ├── stale.yml │ ├── update-jdk-versions.yml │ └── v2-protocol-tests.yml ├── .gitignore ├── .muse/ │ └── advisor.md ├── AGENTS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchmarks/ │ ├── agent-benchmark/ │ │ ├── build.gradle │ │ └── src/ │ │ └── jmh/ │ │ ├── btrace/ │ │ │ └── TraceScript.java │ │ └── java/ │ │ └── benchmark/ │ │ └── BTraceBench.java │ └── runtime-benchmarks/ │ ├── build.gradle │ └── src/ │ └── jmh/ │ ├── btrace/ │ │ └── TraceScript.java │ └── java/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── bench/ │ ├── ClassFilterBenchmark.java │ ├── DispatchBenchmark.java │ ├── OnMethodTemplateBenchmark.java │ ├── ProbeLoadingBenchmark.java │ ├── ProfilerBenchmark.java │ ├── StatsdBenchmark.java │ └── StringOpBenchmark.java ├── btrace-agent/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── agent/ │ │ │ ├── Client.java │ │ │ ├── ClientContext.java │ │ │ ├── FileClient.java │ │ │ ├── Main.java │ │ │ ├── PerfReaderImpl.java │ │ │ ├── RemoteClient.java │ │ │ └── TraceOutputWriter.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── MANIFEST.MF │ └── test/ │ └── java/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── agent/ │ └── MainTest.java ├── btrace-api/ │ └── build.gradle ├── btrace-boot/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── boot/ │ ├── Loader.java │ ├── MaskedClassLoader.java │ └── MaskedJarUtils.java ├── btrace-bootstrap/ │ └── build.gradle ├── btrace-client/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── client/ │ │ ├── Client.java │ │ ├── JpsUtils.java │ │ ├── Main.java │ │ └── ProbePrinter.java │ └── test/ │ └── java/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── client/ │ ├── ClientTest.java │ └── MainTest.java ├── btrace-compiler/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── compiler/ │ │ ├── AnnotationSerializer.java │ │ ├── ClassDataJavaFileObject.java │ │ ├── Compiler.java │ │ ├── CompilerClassWriter.java │ │ ├── CompilerHelper.java │ │ ├── ConcatenatingReader.java │ │ ├── MaskedJavaFileManager.java │ │ ├── MemoryJavaFileManager.java │ │ ├── PCPP.java │ │ ├── PackGenerator.java │ │ ├── Postprocessor.java │ │ ├── Printer.java │ │ ├── Verifier.java │ │ ├── VerifierVisitor.java │ │ └── oneliner/ │ │ ├── OnelinerAST.java │ │ ├── OnelinerCodeGenerator.java │ │ ├── OnelinerException.java │ │ ├── OnelinerLexer.java │ │ ├── OnelinerParser.java │ │ └── OnelinerValidator.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── compiler/ │ │ ├── JfrEventsTest.java │ │ ├── TypeErasureTest.java │ │ └── oneliner/ │ │ ├── OnelinerCodeGeneratorTest.java │ │ ├── OnelinerIntegrationTest.java │ │ └── OnelinerParserTest.java │ └── resources/ │ ├── HistoProbe.java │ ├── JfrEventsProbe.java │ └── MetricsExtensionProbe.java ├── btrace-core/ │ ├── JMH_BENCHMARKS.md │ ├── build.gradle │ └── src/ │ ├── jmh/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── core/ │ │ └── comm/ │ │ └── v2/ │ │ └── BinaryProtocolBenchmark.java │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ ├── core/ │ │ │ │ ├── Args.java │ │ │ │ ├── ArgsMap.java │ │ │ │ ├── BTraceRuntime.java │ │ │ │ ├── BTraceRuntimeBridge.java │ │ │ │ ├── BTraceUtils.java │ │ │ │ ├── CircularBuffer.java │ │ │ │ ├── DebugSupport.java │ │ │ │ ├── Function.java │ │ │ │ ├── HandlerRepository.java │ │ │ │ ├── Messages.java │ │ │ │ ├── MethodID.java │ │ │ │ ├── PrefixMap.java │ │ │ │ ├── Profiler.java │ │ │ │ ├── SharedSettings.java │ │ │ │ ├── VerifierException.java │ │ │ │ ├── annotations/ │ │ │ │ │ ├── BTrace.java │ │ │ │ │ ├── DTrace.java │ │ │ │ │ ├── DTraceRef.java │ │ │ │ │ ├── Duration.java │ │ │ │ │ ├── Event.java │ │ │ │ │ ├── Export.java │ │ │ │ │ ├── Injected.java │ │ │ │ │ ├── InjectionMode.java │ │ │ │ │ ├── Kind.java │ │ │ │ │ ├── Level.java │ │ │ │ │ ├── Location.java │ │ │ │ │ ├── OnError.java │ │ │ │ │ ├── OnEvent.java │ │ │ │ │ ├── OnExit.java │ │ │ │ │ ├── OnLowMemory.java │ │ │ │ │ ├── OnMethod.java │ │ │ │ │ ├── OnProbe.java │ │ │ │ │ ├── OnTimer.java │ │ │ │ │ ├── PeriodicEvent.java │ │ │ │ │ ├── ProbeClassName.java │ │ │ │ │ ├── ProbeMethodName.java │ │ │ │ │ ├── Property.java │ │ │ │ │ ├── Return.java │ │ │ │ │ ├── Sampled.java │ │ │ │ │ ├── Self.java │ │ │ │ │ ├── TLS.java │ │ │ │ │ ├── TargetInstance.java │ │ │ │ │ ├── TargetMethodOrField.java │ │ │ │ │ └── Where.java │ │ │ │ ├── comm/ │ │ │ │ │ ├── BinaryWireProtocol.java │ │ │ │ │ ├── Command.java │ │ │ │ │ ├── CommandListener.java │ │ │ │ │ ├── DataCommand.java │ │ │ │ │ ├── DisconnectCommand.java │ │ │ │ │ ├── ErrorCommand.java │ │ │ │ │ ├── EventCommand.java │ │ │ │ │ ├── ExitCommand.java │ │ │ │ │ ├── GridDataCommand.java │ │ │ │ │ ├── InstrumentCommand.java │ │ │ │ │ ├── JavaSerializationProtocol.java │ │ │ │ │ ├── ListFailedExtensionsCommand.java │ │ │ │ │ ├── ListProbesCommand.java │ │ │ │ │ ├── MessageCommand.java │ │ │ │ │ ├── NumberDataCommand.java │ │ │ │ │ ├── NumberMapDataCommand.java │ │ │ │ │ ├── PrintableCommand.java │ │ │ │ │ ├── ProtocolConfig.java │ │ │ │ │ ├── ProtocolNegotiator.java │ │ │ │ │ ├── ProtocolVersion.java │ │ │ │ │ ├── ReconnectCommand.java │ │ │ │ │ ├── RenameCommand.java │ │ │ │ │ ├── RetransformClassNotification.java │ │ │ │ │ ├── RetransformationStartNotification.java │ │ │ │ │ ├── SetSettingsCommand.java │ │ │ │ │ ├── StatusCommand.java │ │ │ │ │ ├── StringMapDataCommand.java │ │ │ │ │ ├── WireIO.java │ │ │ │ │ ├── WireProtocol.java │ │ │ │ │ └── v2/ │ │ │ │ │ ├── BinaryClient.java │ │ │ │ │ ├── BinaryCommand.java │ │ │ │ │ ├── BinaryDataCommand.java │ │ │ │ │ ├── BinaryDisconnectCommand.java │ │ │ │ │ ├── BinaryErrorCommand.java │ │ │ │ │ ├── BinaryEventCommand.java │ │ │ │ │ ├── BinaryExitCommand.java │ │ │ │ │ ├── BinaryGridDataCommand.java │ │ │ │ │ ├── BinaryInstrumentCommand.java │ │ │ │ │ ├── BinaryListFailedExtensionsCommand.java │ │ │ │ │ ├── BinaryListProbesCommand.java │ │ │ │ │ ├── BinaryMessageCommand.java │ │ │ │ │ ├── BinaryNumberDataCommand.java │ │ │ │ │ ├── BinaryNumberMapDataCommand.java │ │ │ │ │ ├── BinaryProtocol.java │ │ │ │ │ ├── BinaryReconnectCommand.java │ │ │ │ │ ├── BinaryRenameCommand.java │ │ │ │ │ ├── BinaryRetransformClassNotification.java │ │ │ │ │ ├── BinaryRetransformationStartNotification.java │ │ │ │ │ ├── BinarySetSettingsCommand.java │ │ │ │ │ ├── BinaryStatusCommand.java │ │ │ │ │ ├── BinaryStringCommand.java │ │ │ │ │ ├── BinaryStringMapDataCommand.java │ │ │ │ │ ├── BinaryWireIO.java │ │ │ │ │ ├── CommandAdapter.java │ │ │ │ │ ├── CommandDeserializationException.java │ │ │ │ │ ├── MalformedCommandException.java │ │ │ │ │ ├── NumberEncoding.java │ │ │ │ │ ├── ProtocolVersionMismatchException.java │ │ │ │ │ ├── README.md │ │ │ │ │ ├── RemoteException.java │ │ │ │ │ └── ScalarEncoding.java │ │ │ │ ├── extensions/ │ │ │ │ │ ├── Extension.java │ │ │ │ │ ├── ExtensionContext.java │ │ │ │ │ ├── ExtensionDescriptor.java │ │ │ │ │ ├── ExtensionException.java │ │ │ │ │ ├── ExtensionMeta.java │ │ │ │ │ ├── Permission.java │ │ │ │ │ ├── PermissionSet.java │ │ │ │ │ └── ServiceDescriptor.java │ │ │ │ ├── handlers/ │ │ │ │ │ ├── ErrorHandler.java │ │ │ │ │ ├── EventHandler.java │ │ │ │ │ ├── ExitHandler.java │ │ │ │ │ ├── LowMemoryHandler.java │ │ │ │ │ └── TimerHandler.java │ │ │ │ ├── jfr/ │ │ │ │ │ └── JfrEvent.java │ │ │ │ └── types/ │ │ │ │ ├── AnyType.java │ │ │ │ ├── BTraceCollection.java │ │ │ │ ├── BTraceDeque.java │ │ │ │ └── BTraceMap.java │ │ │ └── runtime/ │ │ │ ├── BTraceRuntimeAccess.java │ │ │ └── LinkingFlag.java │ │ └── resources/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── core/ │ │ ├── annotations/ │ │ │ └── jaxb.index │ │ └── messages.properties │ └── test/ │ └── java/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── core/ │ ├── CircularBufferTest.java │ ├── MethodIDTest.java │ ├── PrefixMapTest.java │ ├── ReflectiveFieldAccessTest.java │ └── comm/ │ ├── InstrumentCommandTest.java │ ├── JavaSerializationProtocolLeakTest.java │ ├── ListProbesCommandConcurrencyTest.java │ ├── NullSafetyTest.java │ ├── ProtocolConfigTest.java │ ├── ProtocolNegotiatorTest.java │ ├── TypeSafetyTest.java │ ├── WireProtocolSessionTest.java │ ├── WireProtocolTest.java │ └── v2/ │ ├── BinaryProtocolEdgeCasesTest.java │ ├── BinaryProtocolPerformanceTest.java │ └── BinaryProtocolTest.java ├── btrace-dist/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── resources/ │ ├── COPYRIGHT │ ├── LICENSE │ ├── LICENSE-3RD-PARTY.txt │ ├── THIRDPARTYLICENSEREADME.txt │ ├── bin/ │ │ ├── btrace │ │ ├── btrace.bat │ │ ├── btracec │ │ ├── btracec.bat │ │ ├── btracep │ │ ├── btracep.bat │ │ ├── btracer │ │ ├── btracer.bat │ │ ├── btracex │ │ └── btracex.bat │ ├── conf/ │ │ └── extensions.conf │ └── samples/ │ ├── AWTEventTracer.java │ ├── AllCalls1.java │ ├── AllCalls1Sampled.java │ ├── AllCalls2.java │ ├── AllCalls2Sampled.java │ ├── AllCalls3.java │ ├── AllCalls3Sampled.java │ ├── AllLines.java │ ├── AllMethods.java │ ├── AllMethods1.java │ ├── AllMethodsLevels.java │ ├── AllMethodsSampled.java │ ├── AllSync.java │ ├── ArgArray.java │ ├── Classload.java │ ├── CommandArg.java │ ├── DTraceInline.java │ ├── DTraceRefDemo.java │ ├── Deadlock.java │ ├── FileTracker.java │ ├── FileTrackerJfr.java │ ├── FinalizeTracker.java │ ├── HistoOnEvent.java │ ├── Histogram.java │ ├── HistogramBean.java │ ├── JInfo.java │ ├── JMap.java │ ├── JStack.java │ ├── LogTracer.java │ ├── MemAlerter.java │ ├── Memory.java │ ├── MultiClass.java │ ├── NewArray.java │ ├── NewComponent.java │ ├── OnThrow.java │ ├── ProbeArgs.java │ ├── ProbeExit.java │ ├── Profiling.java │ ├── Sizeof.java │ ├── SocketTracker.java │ ├── SocketTracker1.java │ ├── SubtypeTracer.java │ ├── SysProp.java │ ├── Test.java │ ├── ThreadBean.java │ ├── ThreadCounter.java │ ├── ThreadCounterBean.java │ ├── ThreadStart.java │ ├── Timers.java │ ├── URLTracker.java │ ├── WebServiceTracker.java │ ├── btracedefs.h │ ├── classload.d │ ├── java.net.socket.xml │ ├── jthread.d │ ├── jurls.d │ └── syscalls.d ├── btrace-dtrace/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── dtrace/ │ │ │ ├── DTrace.java │ │ │ ├── DTraceCommand.java │ │ │ ├── DTraceConsumerCommand.java │ │ │ ├── DTraceDataCommand.java │ │ │ ├── DTraceDropCommand.java │ │ │ ├── DTraceErrorCommand.java │ │ │ ├── DTraceExtension.java │ │ │ ├── DTraceStartCommand.java │ │ │ └── DTraceStopCommand.java │ │ ├── native/ │ │ │ ├── btrace.c │ │ │ └── btraced.d │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.openjdk.btrace.core.extensions.Extension │ └── mock/ │ └── java/ │ └── org/ │ └── opensolaris/ │ └── os/ │ └── dtrace/ │ ├── Aggregate.java │ ├── Aggregation.java │ ├── AggregationRecord.java │ ├── AggregationValue.java │ ├── Consumer.java │ ├── ConsumerAdapter.java │ ├── ConsumerEvent.java │ ├── ConsumerException.java │ ├── ConsumerListener.java │ ├── DTraceException.java │ ├── DataEvent.java │ ├── Drop.java │ ├── DropEvent.java │ ├── ErrorEvent.java │ ├── ExceptionHandler.java │ ├── LocalConsumer.java │ ├── Option.java │ ├── Probe.java │ ├── ProbeData.java │ ├── ProbeDescription.java │ ├── Program.java │ ├── Record.java │ └── Tuple.java ├── btrace-ext-cli/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── extcli/ │ │ ├── ExtensionInspector.java │ │ ├── ExtensionLister.java │ │ ├── ExtensionReport.java │ │ ├── Installer.java │ │ ├── Main.java │ │ ├── PolicyFile.java │ │ ├── RepoBrowser.java │ │ ├── RepoScanner.java │ │ └── tui/ │ │ ├── ExtRepoBrowser.java │ │ ├── ExtensionInspectorLite.java │ │ ├── PolicyFileLite.java │ │ ├── RepoScannerLite.java │ │ └── TuiState.java │ └── test/ │ └── java/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── extcli/ │ ├── ExtensionInspectorTest.java │ ├── ExtensionListerTest.java │ ├── InstallerTest.java │ ├── MainTest.java │ ├── PolicyFileTest.java │ └── TestExtensionBuilder.java ├── btrace-extension/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── extension/ │ │ ├── ExtensionBridge.java │ │ ├── ExtensionDescriptorDTO.java │ │ ├── ExtensionLoader.java │ │ ├── ExtensionRegistry.java │ │ ├── ExtensionRepository.java │ │ ├── PermissionPolicy.java │ │ ├── ServiceDeclarationRegistry.java │ │ └── impl/ │ │ ├── ExtensionBridgeImpl.java │ │ ├── ExtensionClassLoader.java │ │ ├── ExtensionConfig.java │ │ ├── ExtensionLoaderImpl.java │ │ ├── ExtensionMetadata.java │ │ ├── FileSystemExtensionRepository.java │ │ └── NestedJarExtensionClassLoader.java │ └── test/ │ ├── java/ │ │ ├── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── extension/ │ │ │ ├── ExtensionBridgeImplPolicyTest.java │ │ │ └── ExtensionLoaderImplConcurrencyTest.java │ │ └── test/ │ │ ├── ext/ │ │ │ ├── Service.java │ │ │ ├── ServiceImpl.java │ │ │ └── SpiImpl.java │ │ └── ext2/ │ │ ├── Service2.java │ │ └── Service2Impl.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── test.ext.Service ├── btrace-extensions/ │ ├── btrace-metrics/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── api/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── metrics/ │ │ │ ├── MetricsService.java │ │ │ ├── histogram/ │ │ │ │ ├── HistogramConfig.java │ │ │ │ ├── HistogramConfigBuilder.java │ │ │ │ ├── HistogramMetric.java │ │ │ │ └── HistogramSnapshot.java │ │ │ ├── package-info.java │ │ │ └── stats/ │ │ │ ├── StatsMetric.java │ │ │ └── StatsSnapshot.java │ │ ├── impl/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── metrics/ │ │ │ ├── Metric.java │ │ │ ├── MetricsServiceImpl.java │ │ │ ├── histogram/ │ │ │ │ ├── HistogramConfigBuilderImpl.java │ │ │ │ ├── HistogramConfigImpl.java │ │ │ │ ├── HistogramMetricImpl.java │ │ │ │ └── HistogramSnapshotImpl.java │ │ │ ├── registry/ │ │ │ │ └── MetricRegistry.java │ │ │ └── stats/ │ │ │ ├── StatsMetricImpl.java │ │ │ └── StatsSnapshotImpl.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── metrics/ │ │ ├── HistogramMetricTest.java │ │ └── StatsMetricTest.java │ ├── btrace-statsd/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── api/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── statsd/ │ │ │ ├── Statsd.java │ │ │ └── package-info.java │ │ └── impl/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── statsd/ │ │ └── StatsdImpl.java │ ├── btrace-utils/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── api/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── utils/ │ │ │ ├── PrinterService.java │ │ │ └── package-info.java │ │ └── impl/ │ │ └── java/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── utils/ │ │ └── PrinterServiceImpl.java │ └── build.gradle ├── btrace-gradle-plugin/ │ ├── README.md │ ├── build.gradle │ └── src/ │ └── main/ │ └── groovy/ │ └── org/ │ └── openjdk/ │ └── btrace/ │ └── gradle/ │ ├── BTraceExtensionPlugin.groovy │ └── PermissionScanner.groovy ├── btrace-instr/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── instr/ │ │ │ ├── ArrayAccessInstrumentor.java │ │ │ ├── ArrayAllocInstrumentor.java │ │ │ ├── Assembler.java │ │ │ ├── BTraceBCPClassLoader.java │ │ │ ├── BTraceClassReader.java │ │ │ ├── BTraceClassWriter.java │ │ │ ├── BTraceMethodNode.java │ │ │ ├── BTraceMethodVisitor.java │ │ │ ├── BTraceProbe.java │ │ │ ├── BTraceProbeFactory.java │ │ │ ├── BTraceProbeNode.java │ │ │ ├── BTraceProbePersisted.java │ │ │ ├── BTraceProbeSupport.java │ │ │ ├── BTraceTransformer.java │ │ │ ├── BailoutException.java │ │ │ ├── CallGraph.java │ │ │ ├── CatchInstrumentor.java │ │ │ ├── ClassCache.java │ │ │ ├── ClassFilter.java │ │ │ ├── ClassInfo.java │ │ │ ├── Constants.java │ │ │ ├── ErrorReturnInstrumentor.java │ │ │ ├── FieldAccessInstrumentor.java │ │ │ ├── HandlerRepositoryImpl.java │ │ │ ├── InstrPackGenerator.java │ │ │ ├── InstrumentUtils.java │ │ │ ├── InstrumentationException.java │ │ │ ├── InstrumentingMethodVisitor.java │ │ │ ├── Instrumentor.java │ │ │ ├── Level.java │ │ │ ├── LineNumberInstrumentor.java │ │ │ ├── LinkerInstrumentor.java │ │ │ ├── Location.java │ │ │ ├── MethodCallInstrumentor.java │ │ │ ├── MethodEntryExitInstrumentor.java │ │ │ ├── MethodEntryInstrumentor.java │ │ │ ├── MethodInstrumentor.java │ │ │ ├── MethodInstrumentorHelper.java │ │ │ ├── MethodReturnInstrumentor.java │ │ │ ├── MethodTracker.java │ │ │ ├── MethodTrackingContext.java │ │ │ ├── MethodVerifier.java │ │ │ ├── ObjectAllocInstrumentor.java │ │ │ ├── OnMethod.java │ │ │ ├── OnProbe.java │ │ │ ├── Preprocessor.java │ │ │ ├── ProbeDescriptor.java │ │ │ ├── ProbeDescriptorLoader.java │ │ │ ├── ProbeDump.java │ │ │ ├── ProbeRenameVisitor.java │ │ │ ├── ProbeUpgradeVisitor_1_2.java │ │ │ ├── RandomIntProvider.java │ │ │ ├── SpecialParameterHolder.java │ │ │ ├── StackTrackingMethodVisitor.java │ │ │ ├── SynchronizedInstrumentor.java │ │ │ ├── ThrowInstrumentor.java │ │ │ ├── TypeCheckInstrumentor.java │ │ │ ├── TypeUtils.java │ │ │ ├── VariableMapper.java │ │ │ ├── Verifier.java │ │ │ └── random/ │ │ │ ├── SharedRandomIntProvider.java │ │ │ └── ThreadLocalRandomIntProvider.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── org.openjdk.btrace.compiler.PackGenerator │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── instr/ │ │ └── jaxb.index │ └── test/ │ ├── btrace/ │ │ ├── ExportTest.java │ │ ├── InterestingVarsTest.java │ │ ├── OnProbeTest.java │ │ ├── OnTimerTest.java │ │ ├── TLSTest.java │ │ ├── TraceAllTest.java │ │ ├── issues/ │ │ │ ├── BTRACE106.java │ │ │ ├── BTRACE189.java │ │ │ ├── BTRACE22.java │ │ │ ├── BTRACE256.java │ │ │ ├── BTRACE28.java │ │ │ ├── BTRACE53.java │ │ │ ├── BTRACE69.java │ │ │ ├── BTRACE87.java │ │ │ ├── BTRACE_333.java │ │ │ └── TezSplitter.java │ │ ├── onmethod/ │ │ │ ├── AllLines.java │ │ │ ├── AnytypeArgs.java │ │ │ ├── AnytypeArgsNoSelf.java │ │ │ ├── Args.java │ │ │ ├── Args2Sampled.java │ │ │ ├── ArgsDuration.java │ │ │ ├── ArgsDuration2.java │ │ │ ├── ArgsDuration2Err.java │ │ │ ├── ArgsDuration2Sampled.java │ │ │ ├── ArgsDurationBoxed.java │ │ │ ├── ArgsDurationBoxedErr.java │ │ │ ├── ArgsDurationConstructor.java │ │ │ ├── ArgsDurationConstructorErr.java │ │ │ ├── ArgsDurationErr.java │ │ │ ├── ArgsDurationMultiReturn.java │ │ │ ├── ArgsDurationSampled.java │ │ │ ├── ArgsNoSelf.java │ │ │ ├── ArgsReturn.java │ │ │ ├── ArgsReturnAugmented.java │ │ │ ├── ArgsReturnAugmented1.java │ │ │ ├── ArgsReturnBoxed.java │ │ │ ├── ArgsReturnSampled.java │ │ │ ├── ArgsReturnTypeMatch.java │ │ │ ├── ArgsReturnTypeNoMatch.java │ │ │ ├── ArgsReturnVoid.java │ │ │ ├── ArgsSampled.java │ │ │ ├── ArgsSampledAdaptive.java │ │ │ ├── ArgsSampledNoSampling.java │ │ │ ├── ArgsShared.java │ │ │ ├── ArgsSigMatch.java │ │ │ ├── ArgsUnsafe.java │ │ │ ├── ArrayGetAfter.java │ │ │ ├── ArrayGetAfterAny.java │ │ │ ├── ArrayGetBefore.java │ │ │ ├── ArrayGetBeforeAny.java │ │ │ ├── ArraySetAfter.java │ │ │ ├── ArraySetAfterAny.java │ │ │ ├── ArraySetBefore.java │ │ │ ├── ArraySetBeforeAny.java │ │ │ ├── Catch.java │ │ │ ├── CheckcastAfter.java │ │ │ ├── CheckcastBefore.java │ │ │ ├── ConstructorArgs.java │ │ │ ├── Error.java │ │ │ ├── ErrorCaught.java │ │ │ ├── ErrorDuration.java │ │ │ ├── FieldGetAfter.java │ │ │ ├── FieldGetAfterStatic.java │ │ │ ├── FieldGetBefore.java │ │ │ ├── FieldGetBeforeStatic.java │ │ │ ├── FieldSetAfter.java │ │ │ ├── FieldSetAfterStatic.java │ │ │ ├── FieldSetBefore.java │ │ │ ├── FieldSetBeforeStatic.java │ │ │ ├── InstanceofAfter.java │ │ │ ├── InstanceofBefore.java │ │ │ ├── Line.java │ │ │ ├── MatchAnnotated.java │ │ │ ├── MatchAnnotatedRegex.java │ │ │ ├── MatchDerived.java │ │ │ ├── MethodCall.java │ │ │ ├── MethodCallDuration.java │ │ │ ├── MethodCallDuration2.java │ │ │ ├── MethodCallDurationSampled.java │ │ │ ├── MethodCallDurationSampledMulti.java │ │ │ ├── MethodCallNoArgs.java │ │ │ ├── MethodCallReturn.java │ │ │ ├── MethodCallReturnAugmented.java │ │ │ ├── MethodCallReturnAugmented1.java │ │ │ ├── MethodCallSampled.java │ │ │ ├── MethodCallSampledAdaptive.java │ │ │ ├── MethodCallStatic.java │ │ │ ├── NativeWithReturn.java │ │ │ ├── NativeWithoutReturn.java │ │ │ ├── NewAfter.java │ │ │ ├── NewArrayIntAfter.java │ │ │ ├── NewArrayIntBefore.java │ │ │ ├── NewArrayStringAfter.java │ │ │ ├── NewArrayStringBefore.java │ │ │ ├── NewBefore.java │ │ │ ├── NoArgs.java │ │ │ ├── NoArgsEntryReturn.java │ │ │ ├── NoArgsEntryReturnNoCapture.java │ │ │ ├── ServicesTest.java │ │ │ ├── StaticArgs.java │ │ │ ├── StaticArgsReturn.java │ │ │ ├── StaticArgsSelf.java │ │ │ ├── StaticMethodCall.java │ │ │ ├── StaticMethodCallStatic.java │ │ │ ├── StaticNoArgs.java │ │ │ ├── StaticNoArgsSelf.java │ │ │ ├── SyncEntry.java │ │ │ ├── SyncExit.java │ │ │ ├── SyncMEntry.java │ │ │ ├── SyncMExit.java │ │ │ ├── Throw.java │ │ │ └── leveled/ │ │ │ ├── AnytypeArgs.java │ │ │ ├── AnytypeArgsNoSelf.java │ │ │ ├── Args.java │ │ │ ├── Args2Sampled.java │ │ │ ├── ArgsDuration.java │ │ │ ├── ArgsDuration2.java │ │ │ ├── ArgsDuration2Err.java │ │ │ ├── ArgsDuration2Sampled.java │ │ │ ├── ArgsDurationBoxed.java │ │ │ ├── ArgsDurationBoxedErr.java │ │ │ ├── ArgsDurationConstructor.java │ │ │ ├── ArgsDurationConstructorErr.java │ │ │ ├── ArgsDurationErr.java │ │ │ ├── ArgsDurationMultiReturn.java │ │ │ ├── ArgsDurationSampled.java │ │ │ ├── ArgsNoSelf.java │ │ │ ├── ArgsReturn.java │ │ │ ├── ArgsReturnAugmented.java │ │ │ ├── ArgsReturnAugmented1.java │ │ │ ├── ArgsReturnSampled.java │ │ │ ├── ArgsSampled.java │ │ │ ├── ArgsSampledAdaptive.java │ │ │ ├── ArgsSampledNoSampling.java │ │ │ ├── ArgsShared.java │ │ │ ├── ArgsUnsafe.java │ │ │ ├── ArrayGetAfter.java │ │ │ ├── ArrayGetAfterAny.java │ │ │ ├── ArrayGetBefore.java │ │ │ ├── ArrayGetBeforeAny.java │ │ │ ├── ArraySetAfter.java │ │ │ ├── ArraySetAfterAny.java │ │ │ ├── ArraySetBefore.java │ │ │ ├── ArraySetBeforeAny.java │ │ │ ├── Catch.java │ │ │ ├── CheckcastAfter.java │ │ │ ├── CheckcastBefore.java │ │ │ ├── ConstructorArgs.java │ │ │ ├── Error.java │ │ │ ├── ErrorCaught.java │ │ │ ├── ErrorDuration.java │ │ │ ├── FieldGetAfter.java │ │ │ ├── FieldGetAfterStatic.java │ │ │ ├── FieldGetBefore.java │ │ │ ├── FieldGetBeforeStatic.java │ │ │ ├── FieldSetAfter.java │ │ │ ├── FieldSetAfterStatic.java │ │ │ ├── FieldSetBefore.java │ │ │ ├── FieldSetBeforeStatic.java │ │ │ ├── InstanceofAfter.java │ │ │ ├── InstanceofBefore.java │ │ │ ├── Line.java │ │ │ ├── MatchDerived.java │ │ │ ├── MethodCall.java │ │ │ ├── MethodCallDuration.java │ │ │ ├── MethodCallDuration2.java │ │ │ ├── MethodCallDurationSampled.java │ │ │ ├── MethodCallDurationSampledMulti.java │ │ │ ├── MethodCallNoArgs.java │ │ │ ├── MethodCallReturn.java │ │ │ ├── MethodCallReturnAugmented.java │ │ │ ├── MethodCallReturnAugmented1.java │ │ │ ├── MethodCallSampled.java │ │ │ ├── MethodCallSampledAdaptive.java │ │ │ ├── MethodCallStatic.java │ │ │ ├── NativeWithReturn.java │ │ │ ├── NativeWithoutReturn.java │ │ │ ├── NewAfter.java │ │ │ ├── NewArrayIntAfter.java │ │ │ ├── NewArrayIntBefore.java │ │ │ ├── NewArrayStringAfter.java │ │ │ ├── NewArrayStringBefore.java │ │ │ ├── NewBefore.java │ │ │ ├── NoArgs.java │ │ │ ├── NoArgsEntryReturn.java │ │ │ ├── StaticArgs.java │ │ │ ├── StaticArgsReturn.java │ │ │ ├── StaticArgsSelf.java │ │ │ ├── StaticMethodCall.java │ │ │ ├── StaticMethodCallStatic.java │ │ │ ├── StaticNoArgs.java │ │ │ ├── StaticNoArgsSelf.java │ │ │ ├── SyncEntry.java │ │ │ ├── SyncExit.java │ │ │ ├── SyncMEntry.java │ │ │ ├── SyncMExit.java │ │ │ └── Throw.java │ │ ├── org.openjdk.btrace.xml │ │ └── verifier/ │ │ └── VerifierScript.java │ ├── java/ │ │ ├── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ ├── ArgsMapTest.java │ │ │ └── instr/ │ │ │ ├── BTraceProbeFactoryTest.java │ │ │ ├── BTraceTransformerEarlyExitTest.java │ │ │ ├── CallGraphTest.java │ │ │ ├── ClassCacheTest.java │ │ │ ├── ClassFilterSensitiveTest.java │ │ │ ├── ClassInfoTest.java │ │ │ ├── ExtensionBootstrapTest.java │ │ │ ├── HandlerRepositoryImplTest.java │ │ │ ├── InstrStackTest.java │ │ │ ├── InstrumentUtilsTest.java │ │ │ ├── InstrumentingMethodVisitorTest.java │ │ │ ├── InstrumentorTestBase.java │ │ │ ├── MethodCounterTest.java │ │ │ ├── OnMethodInstrumenterTest.java │ │ │ ├── OnMethodTest.java │ │ │ ├── ProbeClassUnloadingTest.java │ │ │ ├── ProbeLoaderNewTest.java │ │ │ ├── ProbeLoaderOldTest.java │ │ │ ├── ProbeLoaderUpgradeTest.java │ │ │ ├── StackTrackingMethodVisitorTest.java │ │ │ └── VariableMapperTest.java │ │ └── resources/ │ │ ├── AbstractClass.java │ │ ├── DerivedClass.java │ │ ├── InterestingVarsClass.java │ │ ├── Main.java │ │ ├── OnMethodTest.java │ │ ├── StackTrackerTest.java │ │ ├── TestApp.java │ │ ├── TestPrinter.java │ │ └── issues/ │ │ ├── BTRACE106.java │ │ ├── BTRACE22.java │ │ ├── BTRACE256.java │ │ ├── BTRACE28.java │ │ └── BTRACE87.java │ └── resources/ │ ├── instrumentorTestData/ │ │ └── dynamic/ │ │ ├── ExportTest │ │ ├── OnProbeTest │ │ ├── OnTimerTest │ │ ├── ServicesTest │ │ ├── TLSTest │ │ ├── TraceAllTest │ │ ├── issues/ │ │ │ ├── BTRACE106 │ │ │ ├── BTRACE189 │ │ │ ├── BTRACE22 │ │ │ ├── BTRACE256 │ │ │ ├── BTRACE28 │ │ │ ├── BTRACE53 │ │ │ ├── BTRACE69 │ │ │ ├── BTRACE87 │ │ │ ├── BTRACE_333 │ │ │ ├── InterestingVarsTest │ │ │ └── TezSplitter │ │ └── onmethod/ │ │ ├── AllLines │ │ ├── AnytypeArgs │ │ ├── AnytypeArgsNoSelf │ │ ├── Args │ │ ├── Args2Sampled │ │ ├── ArgsDuration │ │ ├── ArgsDuration2 │ │ ├── ArgsDuration2Err │ │ ├── ArgsDuration2Sampled │ │ ├── ArgsDurationBoxed │ │ ├── ArgsDurationBoxedErr │ │ ├── ArgsDurationConstructor │ │ ├── ArgsDurationConstructorErr │ │ ├── ArgsDurationErr │ │ ├── ArgsDurationMultiReturn │ │ ├── ArgsDurationSampled │ │ ├── ArgsNoSelf │ │ ├── ArgsReturn │ │ ├── ArgsReturnAugmented │ │ ├── ArgsReturnAugmented1 │ │ ├── ArgsReturnBoxed │ │ ├── ArgsReturnSampled │ │ ├── ArgsReturnTypeMatch │ │ ├── ArgsReturnTypeNoMatch │ │ ├── ArgsReturnVoid │ │ ├── ArgsSampled │ │ ├── ArgsSampledAdaptive │ │ ├── ArgsSampledNoSampling │ │ ├── ArgsShared │ │ ├── ArgsSigMatch │ │ ├── ArgsUnsafe │ │ ├── ArrayGetAfter │ │ ├── ArrayGetAfterAny │ │ ├── ArrayGetBefore │ │ ├── ArrayGetBeforeAny │ │ ├── ArraySetAfter │ │ ├── ArraySetAfterAny │ │ ├── ArraySetBefore │ │ ├── ArraySetBeforeAny │ │ ├── Catch │ │ ├── CheckcastAfter │ │ ├── CheckcastBefore │ │ ├── ConstructorArgs │ │ ├── Error │ │ ├── ErrorCaught │ │ ├── ErrorDuration │ │ ├── FieldGetAfter │ │ ├── FieldGetAfterStatic │ │ ├── FieldGetBefore │ │ ├── FieldGetBeforeStatic │ │ ├── FieldSetAfter │ │ ├── FieldSetAfterStatic │ │ ├── FieldSetBefore │ │ ├── FieldSetBeforeStatic │ │ ├── InstanceofAfter │ │ ├── InstanceofBefore │ │ ├── Line │ │ ├── MatchAnnotated │ │ ├── MatchAnnotatedRegex │ │ ├── MatchDerived │ │ ├── MethodCall │ │ ├── MethodCallDuration │ │ ├── MethodCallDuration2 │ │ ├── MethodCallDurationSampled │ │ ├── MethodCallDurationSampledMulti │ │ ├── MethodCallNoArgs │ │ ├── MethodCallReturn │ │ ├── MethodCallReturnAugmented │ │ ├── MethodCallReturnAugmented1 │ │ ├── MethodCallSampled │ │ ├── MethodCallSampledAdaptive │ │ ├── MethodCallStatic │ │ ├── NativeWithReturn │ │ ├── NativeWithoutReturn │ │ ├── NewAfter │ │ ├── NewArrayIntAfter │ │ ├── NewArrayIntBefore │ │ ├── NewArrayStringAfter │ │ ├── NewArrayStringBefore │ │ ├── NewBefore │ │ ├── NoArgs │ │ ├── NoArgsEntryReturn │ │ ├── NoArgsEntryReturnNoCapture │ │ ├── StaticArgs │ │ ├── StaticArgsReturn │ │ ├── StaticArgsSelf │ │ ├── StaticMethodCall │ │ ├── StaticMethodCallStatic │ │ ├── StaticNoArgs │ │ ├── StaticNoArgsSelf │ │ ├── SyncEntry │ │ ├── SyncExit │ │ ├── SyncMEntry │ │ ├── SyncMExit │ │ ├── Throw │ │ └── leveled/ │ │ ├── AnytypeArgs │ │ ├── AnytypeArgsNoSelf │ │ ├── Args │ │ ├── Args2Sampled │ │ ├── ArgsDuration │ │ ├── ArgsDuration2 │ │ ├── ArgsDuration2Err │ │ ├── ArgsDuration2Sampled │ │ ├── ArgsDurationBoxed │ │ ├── ArgsDurationBoxedErr │ │ ├── ArgsDurationConstructor │ │ ├── ArgsDurationConstructorErr │ │ ├── ArgsDurationErr │ │ ├── ArgsDurationMultiReturn │ │ ├── ArgsDurationSampled │ │ ├── ArgsNoSelf │ │ ├── ArgsReturn │ │ ├── ArgsReturnAugmented │ │ ├── ArgsReturnAugmented1 │ │ ├── ArgsReturnSampled │ │ ├── ArgsSampled │ │ ├── ArgsSampledAdaptive │ │ ├── ArgsSampledNoSampling │ │ ├── ArgsShared │ │ ├── ArgsUnsafe │ │ ├── ArrayGetAfter │ │ ├── ArrayGetAfterAny │ │ ├── ArrayGetBefore │ │ ├── ArrayGetBeforeAny │ │ ├── ArraySetAfter │ │ ├── ArraySetAfterAny │ │ ├── ArraySetBefore │ │ ├── ArraySetBeforeAny │ │ ├── Catch │ │ ├── CheckcastAfter │ │ ├── CheckcastBefore │ │ ├── ConstructorArgs │ │ ├── Error │ │ ├── ErrorCaught │ │ ├── ErrorDuration │ │ ├── FieldGetAfter │ │ ├── FieldGetAfterStatic │ │ ├── FieldGetBefore │ │ ├── FieldGetBeforeStatic │ │ ├── FieldSetAfter │ │ ├── FieldSetAfterStatic │ │ ├── FieldSetBefore │ │ ├── FieldSetBeforeStatic │ │ ├── InstanceofAfter │ │ ├── InstanceofBefore │ │ ├── Line │ │ ├── MatchDerived │ │ ├── MethodCall │ │ ├── MethodCallDuration │ │ ├── MethodCallDuration2 │ │ ├── MethodCallDurationSampled │ │ ├── MethodCallDurationSampledMulti │ │ ├── MethodCallNoArgs │ │ ├── MethodCallReturn │ │ ├── MethodCallReturnAugmented │ │ ├── MethodCallReturnAugmented1 │ │ ├── MethodCallSampled │ │ ├── MethodCallSampledAdaptive │ │ ├── MethodCallStatic │ │ ├── NativeWithReturn │ │ ├── NativeWithoutReturn │ │ ├── NewAfter │ │ ├── NewArrayIntAfter │ │ ├── NewArrayIntBefore │ │ ├── NewArrayStringAfter │ │ ├── NewArrayStringBefore │ │ ├── NewBefore │ │ ├── NoArgs │ │ ├── NoArgsEntryReturn │ │ ├── StaticArgs │ │ ├── StaticArgsReturn │ │ ├── StaticArgsSelf │ │ ├── StaticMethodCall │ │ ├── StaticMethodCallStatic │ │ ├── StaticNoArgs │ │ ├── StaticNoArgsSelf │ │ ├── SyncEntry │ │ ├── SyncExit │ │ ├── SyncMEntry │ │ ├── SyncMExit │ │ └── Throw │ ├── packed/ │ │ └── test-pack.jar │ ├── plain.txt │ └── resources/ │ └── classdata/ │ ├── AllStuff.btrc │ ├── BackpackExtensionTest.clazz │ ├── OnMethodTest.btrc │ ├── PackVersion1.btrc │ ├── ProbeScript.btrc │ ├── TezSplitter.clazz │ └── TraceScript.clazz ├── btrace-runtime/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── runtime/ │ │ │ ├── BTraceMBean.java │ │ │ ├── BTraceRuntimeAccessImpl.java │ │ │ ├── BTraceRuntimeImplBase.java │ │ │ ├── BTraceRuntimeImplFactory.java │ │ │ ├── BTraceRuntimeImpl_8.java │ │ │ ├── BTraceRuntimes.java │ │ │ ├── CommandQueue.java │ │ │ ├── DOTWriter.java │ │ │ ├── DotWriterFormatter.java │ │ │ ├── ExitException.java │ │ │ ├── ExtensionContextImpl.java │ │ │ ├── ExtensionIndy.java │ │ │ ├── IndyDispatcher.java │ │ │ ├── Interval.java │ │ │ ├── JfrEventFactoryImpl.java │ │ │ ├── JfrEventImpl.java │ │ │ ├── NullPerfReaderImpl.java │ │ │ ├── PerfReader.java │ │ │ ├── ProbeAnchor.java │ │ │ ├── XMLSerializer.java │ │ │ ├── auxiliary/ │ │ │ │ └── Auxiliary.java │ │ │ └── profiling/ │ │ │ ├── MethodInvocationProfiler.java │ │ │ └── MethodInvocationRecorder.java │ │ ├── java11/ │ │ │ └── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── runtime/ │ │ │ └── BTraceRuntimeImpl_11.java │ │ └── java9/ │ │ └── org/ │ │ └── openjdk/ │ │ └── btrace/ │ │ └── runtime/ │ │ └── BTraceRuntimeImpl_9.java │ └── test/ │ ├── java/ │ │ ├── org/ │ │ │ └── openjdk/ │ │ │ └── btrace/ │ │ │ └── runtime/ │ │ │ ├── ExtensionIndyShimIndexTest.java │ │ │ └── HiddenClassDefineRegressionTest.java │ │ └── test/ │ │ └── shim/ │ │ ├── ShimService.java │ │ ├── ShimServiceNoop.java │ │ └── ShimServiceThrow.java │ └── resources/ │ └── META-INF/ │ └── btrace/ │ └── shims.index ├── btrace-ui/ │ └── build.gradle ├── build.gradle ├── buildSrc/ │ ├── build.gradle │ └── shared.gradle ├── common.gradle ├── doc/ │ └── specs/ │ └── 2026-04-11-btraceiobtrace802-phase-3-invokedynamic-handler-isolation.md ├── docker/ │ ├── .dockerignore │ ├── Dockerfile │ ├── Dockerfile.alpine │ ├── Dockerfile.distroless │ ├── README.md │ └── docker-entrypoint.sh ├── docs/ │ ├── BTraceExtensionDevelopmentGuide.md │ ├── BTraceTutorial.md │ ├── ExtensionInterfaceRules.md │ ├── FAQ.md │ ├── GettingStarted.md │ ├── OnelinerGuide.md │ ├── PermissionPolicy.md │ ├── QuickReference.md │ ├── README.md │ ├── Troubleshooting.md │ ├── architecture/ │ │ ├── BTraceInstrAnalysis.md │ │ ├── ExtensionConfiguration.md │ │ ├── ExtensionInvokeDynamicBridge.md │ │ ├── ExtensionManifestFormat.md │ │ ├── ExtensionStorageDesign.md │ │ ├── MaskedJarArchitecture.md │ │ └── Version2ProtocolArchitecture.md │ ├── plans/ │ │ ├── 2026-04-20-NEXT-SESSION-PLAN.md │ │ ├── 2026-04-20-probe-class-unloading-RESUME.md │ │ ├── 2026-04-20-probe-class-unloading.md │ │ ├── DistributionRestructuring.md │ │ └── TestCoverageImprovementPlan.md │ ├── releasing.md │ └── samples/ │ └── permissions.properties ├── gradle/ │ └── spotless.gradle ├── gradlew ├── gradlew.bat ├── integration-tests/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── dummy/ │ │ ├── SimpleEvent.java │ │ └── SimplePeriodicEvent.java │ └── test/ │ ├── btrace/ │ │ ├── ExtensionLifecycleErrorTest.java │ │ ├── ExtensionLifecycleFullTest.java │ │ ├── ExtensionLifecycleMultipleTest.java │ │ ├── ExtensionLifecycleTest.java │ │ ├── JfrTest.java │ │ ├── MetricsTest.java │ │ ├── OSMBeanTest.java │ │ ├── OnExitTest.java │ │ ├── OnMethodLevelTest.java │ │ ├── OnMethodReturnTest.java │ │ ├── OnMethodSubclassTest.java │ │ ├── OnMethodTest.java │ │ ├── OnProbeTest.java │ │ ├── OnTimerArgTest.java │ │ ├── OnTimerTest.java │ │ ├── PerfCounterTest.java │ │ ├── ProbeArgsTest.java │ │ ├── ThreadStart.java │ │ ├── TraceAllTest.java │ │ ├── issues/ │ │ │ └── BTRACE400.java │ │ └── org.openjdk.btrace.xml │ ├── java/ │ │ ├── resources/ │ │ │ ├── Main.java │ │ │ ├── TestApp.java │ │ │ ├── TestPrinter.java │ │ │ └── ThreadSpawner.java │ │ └── tests/ │ │ ├── BTraceFunctionalTests.java │ │ ├── ExtensionLifecycleIntegrationTest.java │ │ ├── JBangAttachDockerTest.java │ │ └── RuntimeTest.java │ └── resources/ │ └── META-INF/ │ └── btrace/ │ └── permissions.properties ├── renovate.json ├── run_tests.sh ├── scripts/ │ ├── release.sh │ └── update-jdk-versions.sh └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: jbachorik issuehunt: jbachorik otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/copilot-instructions.md ================================================ # GitHub Copilot Instructions for BTrace ## About BTrace BTrace is a safe, dynamic tracing tool for the Java platform. It dynamically instruments running Java applications to inject tracing code at runtime using bytecode instrumentation. ## Project Structure - **Gradle multi-module project** with modules named `btrace-*` - **Core modules**: `btrace-core`, `btrace-agent`, `btrace-runtime`, `btrace-client`, `btrace-instr` - **Build artifacts**: `btrace-dist` for distributions - **Tests**: `integration-tests/` for integration tests, `src/test/java` in modules for unit tests - **Documentation**: `docs/` directory ## Architecture Overview - **btrace-agent**: Attachable Java agent with class transformer, manages script lifecycle - **btrace-compiler**: Verifies and compiles BTrace scripts to bytecode - **btrace-instr**: ASM-based instrumentation and weaving utilities - **btrace-runtime**: APIs for scripts (printing, timers, data collection) - **btrace-client**: CLI/attach tooling for sending scripts to target JVM - **services**: SPI for pluggable exporters (e.g., statsd) ## Development Guidelines ### Language & Versions - **Language**: Java - **Source/Target**: Java 8 - **Build toolchain**: JDK 11 - **Test framework**: JUnit Jupiter (JUnit 5) ### Code Style - **Format**: Google Java Format enforced via Spotless - **Packages**: All under `org.openjdk.btrace.*` - **Naming**: Module names follow `btrace-` pattern - **Imports**: Order enforced; remove unused imports - **Comments**: Only add if they match existing style or explain complex logic ### Building & Testing ```bash # Full build with unit tests ./gradlew build # Build distribution only ./gradlew :btrace-dist:build # Run unit tests ./gradlew test # Run integration tests (requires dist build first) ./gradlew -Pintegration test # Format code ./gradlew spotlessApply # Check formatting ./gradlew spotlessCheck ``` ### Important Environment Variables - `JAVA_HOME`: Required for builds - `TEST_JAVA_HOME`: Required for integration tests (typically JDK 11) - `BTRACE_TEST_DEBUG=true`: Enable verbose integration test output - `BTRACE_HOME`: Optional, points to exploded dist ### Testing Best Practices - Unit tests: `src/test/java` with `*Test` suffix - Integration tests: `integration-tests/src/test/java` - BTrace scripts: `integration-tests/src/test/btrace` - Always run relevant tests after making changes - Update golden files when changing instrumentor: `./gradlew test -PupdateTestData` ### Commit & PR Guidelines - **Commit style**: Conventional Commits (e.g., `feat(core): add probe`, `fix(instr): handle null arg`) - **Clear descriptions**: Link related issues - **Tests required**: Update/add tests; ensure CI passes - **Formatting**: Must pass `spotlessCheck` - **No unrelated changes**: Keep changes focused and minimal ## Troubleshooting ### Build Issues - **Attach disabled**: Remove `-XX:+DisableAttachMechanism` from target JVM - **Permission errors**: Attach requires same OS user as target JVM - **Toolchain issues**: Verify `JAVA_HOME` and `TEST_JAVA_HOME` point to valid JDKs ### Restricted Environments ```bash # Use workspace-local Gradle cache GRADLE_USER_HOME=$(pwd)/.gradle-user # Force IPv4 to avoid network interface issues JAVA_TOOL_OPTIONS="-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false" ``` ## Code Generation Tips - **Prefer simplicity**: Simple, performant solutions over complex designs - **Use existing patterns**: Follow patterns from similar code in the repository - **Minimal changes**: Make the smallest possible changes to achieve the goal - **Reuse libraries**: Use ASM for bytecode, JCTools for concurrency, existing BTrace APIs - **No temporary files in repo**: Use `/tmp` for scratch work - **Security**: Never commit secrets; avoid introducing vulnerabilities ## Example BTrace Script Pattern ```java package example; import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.*; @BTrace public class ExampleTrace { @OnMethod(clazz="com.example.Target", method="methodName") public static void onMethod(@ProbeMethodName String method) { println("Called: " + method); } } ``` ## Key Dependencies - **ASM**: Bytecode manipulation - **JCTools**: High-performance concurrent data structures - **hppcrt**: Optimized collections - **JUnit Jupiter**: Testing framework ## Additional Resources - Full guidelines: See `AGENTS.md` in repository root - Tutorial: `docs/BTraceTutorial.md` - Binary releases: https://github.com/btraceio/btrace/releases ================================================ FILE: .github/workflows/README.md ================================================ # BTrace GitHub Actions Workflows ## Overview This directory contains GitHub Actions workflows for continuous integration and testing of the BTrace project, with special focus on the new Binary Protocol v2 implementation. ## Workflows ### 1. `continuous.yml` - Main CI/CD Pipeline **Purpose:** Main continuous integration pipeline for all BTrace components. **Triggers:** - Push to `develop` branch - Pull requests to `develop` - Manual workflow dispatch **Jobs:** - **build:** Compiles the project and runs all tests - Java 11 with Temurin distribution - Parallel build with caching - **V2 Protocol Tests:** Runs dedicated v2 protocol test suite - Uploads dist build artifacts - **test:** Runs integration tests on multiple Java versions - Matrix: Java 8, 11, 17, 21, 25 (EA) - Uses SDKMAN for multiple JDK management - Downloads build artifacts from previous job - Runs integration tests with `-Pintegration` flag - **publish:** Publishes artifacts to Maven Central - Only on `develop` branch - Requires GPG signing credentials - **cleanup:** Removes temporary artifacts **Enhancements for V2 Protocol:** - Added explicit V2 protocol test execution in build job - Tests all v2 packages: `v2.*`, `Protocol*`, `WireProtocol*` ### 2. `v2-protocol-tests.yml` - V2 Protocol Test Suite **Purpose:** Comprehensive testing suite specifically for Binary Protocol v2. **Triggers:** - Push to `develop`, `master`, or `jb/comm_v2` branches - Pull requests to `develop` - Changes to protocol-related files - Manual workflow dispatch - Commit messages containing `[benchmark]` **Jobs:** #### **unit-tests** - Runs all v2 protocol unit tests on Java 11, 17, 21 - Tests binary serialization/deserialization - Validates all 17 command types - Upload test reports as artifacts #### **protocol-negotiation-tests** - Tests protocol version detection - Validates V1/V2 negotiation - Tests configuration management - Verifies magic byte detection #### **edge-case-tests** - Runs 35 edge case scenarios - Tests boundary conditions - Validates large message handling - Tests compression functionality - Unicode and special character handling #### **jmh-benchmarks** (Manual/Opt-in) - Runs JMH performance benchmarks - Triggered by workflow dispatch or `[benchmark]` in commit message - Quick benchmarks: warmup=1, iterations=2, fork=1 - Focuses on serialization performance - Uploads JMH results for 30 days #### **protocol-compatibility** - Tests all 4 compatibility scenarios: - V2 client ↔ V2 agent (optimal) - V1 client ↔ V1 agent (legacy) - V2 client ↔ V1 agent (fallback) - V1 client ↔ V2 agent (detection) - Matrix strategy for comprehensive coverage - Validates backward compatibility #### **test-summary** - Aggregates test results from all jobs - Generates GitHub Step Summary - Reports total/passed/failed counts - Fails if any tests failed #### **code-coverage** - Generates JaCoCo coverage reports - Focuses on `org.openjdk.btrace.core.comm` package - Uploads coverage artifacts for 30 days - Creates coverage summary in step output ### 3. `codeql-analysis.yml` - Security Analysis **Purpose:** CodeQL security scanning for vulnerability detection. **Triggers:** Push/PR to default branch ### 4. `stale.yml` - Issue Management **Purpose:** Automatically marks stale issues and PRs. **Schedule:** Daily at midnight ### 5. `release.yml` - Release Management **Purpose:** Handles the complete release process with a manual checkpoint for Maven Central. **Trigger:** Manual via `scripts/release.sh` or workflow_dispatch **Key Features:** - Stages artifacts to Maven Central (does NOT auto-release) - Waits up to 30 minutes for manual release via Central Portal - Creates GitHub release only after Maven artifacts are available - Updates SDKMan and manages milestones **Manual Checkpoint:** After staging, you must release via [Central Portal](https://central.sonatype.com/publishing/deployments). This allows reviewing artifacts before they become permanent. ## V2 Protocol Test Coverage The workflows ensure comprehensive testing of the v2 protocol implementation: ### Unit Tests (113 total) - ✅ Binary protocol serialization (26 tests) - ✅ Edge cases and boundaries (35 tests) - ✅ Performance comparison (2 tests) - ✅ Protocol negotiation (16 tests) - ✅ Configuration management (18 tests) - ✅ WireProtocol abstraction (16 tests) ### Test Categories 1. **Command Serialization** - All 17 BTrace command types - Round-trip serialization/deserialization - Compression testing 2. **Protocol Negotiation** - V1/V2 auto-detection - Magic byte validation - Configuration-based selection - Stream handling (pushback & mark/reset) 3. **Edge Cases** - Null/empty values - Large messages (10MB) - Unicode and emojis - Malformed data - Numeric boundaries 4. **Performance** - JMH benchmarks (180 configurations) - V1 vs V2 comparison - Compression effectiveness 5. **Compatibility** - V1 ↔ V1 (legacy) - V2 ↔ V2 (optimal) - V1 ↔ V2 (cross-version) - V2 ↔ V1 (fallback) ## Artifacts ### Retained Artifacts (7 days) - Test reports (per Java version) - Negotiation test results - Edge case test results - Compatibility test matrices ### Long-term Artifacts (30 days) - JMH benchmark results - Code coverage reports ### Build Artifacts (1 day) - Distribution builds - Test trace data ## Configuration ### Environment Variables **Build Job:** - Standard Gradle environment - Parallel execution enabled - Build cache enabled **Test Job:** - `TEST_JAVA_HOME`: Set per matrix Java version - SDKMAN for multiple JDK management **Publish Job:** - `GPG_SIGNING_KEY`: GPG key for artifact signing - `GPG_SIGNING_PWD`: GPG key password - `BTRACE_SONATYPE_USER`: Sonatype credentials - `BTRACE_SONATYPE_PWD`: Sonatype credentials ### Gradle Properties for V2 Testing ```bash # Run only v2 tests ./gradlew :btrace-core:test --tests "org.openjdk.btrace.core.comm.v2.*" # Run protocol negotiation tests ./gradlew :btrace-core:test --tests "*Protocol*" # Run specific JMH benchmarks ./gradlew :btrace-core:jmh -PjmhInclude=".*MessageCommand.*" # Generate coverage report ./gradlew :btrace-core:test jacocoTestReport ``` ## JMH Benchmark Workflow ### Trigger Benchmark Run **Option 1: Workflow Dispatch** ```bash # Via GitHub UI: Actions → V2 Protocol Tests → Run workflow ``` **Option 2: Commit Message** ```bash git commit -m "Optimize binary protocol [benchmark]" ``` ### Benchmark Configuration **Quick Benchmarks (CI):** - Warmup: 1 iteration - Measurement: 2 iterations - Forks: 1 - Focus: Serialization methods only **Full Benchmarks (Local):** - Warmup: 3 iterations - Measurement: 5 iterations - Forks: 2 - Coverage: All 180 configurations ## Test Failure Handling ### Automatic Retry - Tests use `--rerun-tasks` to ensure fresh execution - No test result caching to catch flaky tests ### Artifact Upload - All test reports uploaded on failure (`if: always()`) - Artifacts retained for 7 days for analysis ### Summary Generation - Test summary job aggregates all results - Reports failures clearly in GitHub UI - Step summary provides quick overview ## Code Coverage ### JaCoCo Configuration **Focus Area:** - Package: `org.openjdk.btrace.core.comm.**` - Includes v2 protocol, negotiation, and abstraction **Reports Generated:** - XML (for CI tools) - HTML (for human review) - Available in artifacts for 30 days **Coverage Goals:** - Unit test coverage: >90% - Edge case coverage: >80% - Integration coverage: >70% ## Performance Monitoring ### JMH Results - Benchmark results uploaded as artifacts - Compare across runs to detect regressions - Focus on serialization/deserialization speed - Monitor wire size changes ### Expected Metrics - Serialization: 3-6x faster than V1 - Wire size: 2-5x smaller than V1 - Compression: 10-100x size reduction (large messages) ## Maintenance ### Cache Management - Gradle cache keyed by build files hash - Automatic cache eviction after 7 days - Cache size monitored in test job ### Artifact Cleanup - Temporary artifacts cleaned after publish - Test reports retained for 7 days - Performance results retained for 30 days ## Future Enhancements ### Planned Additions 1. **Integration Tests:** - Full client-agent communication tests - Mixed protocol version scenarios - Reconnection testing 2. **Stress Tests:** - High concurrency scenarios - Large message throughput - Memory leak detection 3. **Performance Regression Detection:** - Automated benchmark comparison - Alert on >10% performance degradation - Historical trend analysis 4. **Security Scanning:** - Dependency vulnerability checks - OWASP security analysis - Protocol fuzzing tests ## References - [BTrace v2 Protocol Architecture](../../docs/architecture/Version2ProtocolArchitecture.md) - [Phase 3 Integration Guide](../../docs/architecture/phase3-integration-guide.md) - [V2 Implementation Summary](../../docs/architecture/v2-implementation-summary.md) - [JMH Benchmarks Guide](../../btrace-core/JMH_BENCHMARKS.md) ## Support For workflow issues or questions: 1. Check GitHub Actions logs 2. Review artifact contents 3. Check Gradle build logs 4. Open issue with workflow run link ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ develop, master ] pull_request: # The branches below must be a subset of the branches above branches: [ develop ] schedule: - cron: '23 5 * * 4' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'java' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Build BTrace run: | ./gradlew -x test build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/continuous.yml ================================================ # This workflow will build a Java project with Gradle # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle name: BTrace CI/CD on: push: branches: [ develop ] pull_request: branches: [ develop ] workflow_dispatch: defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest steps: - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up Java if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Checkout uses: actions/checkout@v6 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Generate cache key id: cache-key run: | key="${{ runner.os }}-gradle-1-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}" echo "key=${key}" >> $GITHUB_ENV echo "cache-key=${key}" >> $GITHUB_OUTPUT - name: Cache Gradle uses: actions/cache@v5 with: path: | ~/.gradle/caches/modules-2 ~/.gradle/wrapper key: ${{ env.key }} restore-keys: | ${{ runner.os }}-gradle- - name: Build run: ./gradlew --no-daemon --parallel --build-cache build - name: Run V2 Protocol Tests run: | ./gradlew :btrace-core:test --tests "org.openjdk.btrace.core.comm.v2.*" \ --tests "org.openjdk.btrace.core.comm.Protocol*" \ --tests "org.openjdk.btrace.core.comm.WireProtocol*" - name: Upload dist build data if: always() uses: actions/upload-artifact@v7 with: name: dist-build retention-days: 1 path: | btrace-dist/build btrace-instr/build/classes/traces - name: Upload test trace data if: always() uses: actions/upload-artifact@v7 with: name: test-trace retention-days: 1 path: | btrace-instr/build/classes/traces - name: Archive test reports if: always() uses: actions/upload-artifact@v7 with: name: test-reports path: | **/reports/**/* outputs: cache-key: ${{ steps.cache-key.outputs.cache-key }} test: needs: build runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: java: [ 8.0.482-tem, 11.0.30-tem, 17.0.18-tem, 21.0.10-tem, 25.0.2-tem, 26.ea.35-open ] env: TEST_JAVA_HOME: "/home/runner/.sdkman/candidates/java/${{ matrix.java }}" steps: - name: Checkout uses: actions/checkout@v6 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Prepare OS run: | sudo apt-get update sudo apt-get install -y curl zip unzip - name: Prepare JDK ${{ matrix.java_version }} run: | curl -s "https://get.sdkman.io" | bash source "$HOME/.sdkman/bin/sdkman-init.sh" echo 'n' | sdk install java ${{ matrix.java }} which java echo 'y' | sdk install java 11.0.30-tem - name: Cache Gradle uses: actions/cache@v5 with: path: | ~/.gradle/caches/modules-2 ~/.gradle/wrapper key: ${{ needs.build.outputs.cache-key }} restore-keys: | ${{ runner.os }}-gradle- - name: Download build data uses: actions/download-artifact@v8 with: name: dist-build path: btrace-dist/build - name: Download test trace data uses: actions/download-artifact@v8 with: name: test-trace path: btrace-instr/build/classes/traces - name: Build btrace-instr jar run: | ./gradlew --no-daemon --parallel --build-cache :btrace-instr:jar - name: Run tests run: | set +x ./gradlew --no-daemon --parallel --build-cache -Pintegration -PCI :integration-tests:test - name: Check Gradle cache size run: du -sh ~/.gradle/caches - name: Integration test reports if: always() uses: actions/upload-artifact@v7 with: name: integration-test-reports-${{ matrix.java }} path: | integration-tests/build/reports/**/* - name: Archive binary artifacts if: success() && matrix.java == '11' uses: actions/upload-artifact@v7 with: name: btrace-dist path: | btrace-dist/build/distributions/**/btrace-*-bin*.tar.gz publish: if: github.ref == 'refs/heads/develop' needs: - test - build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up Java if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Cache Gradle uses: actions/cache@v5 with: path: | ~/.gradle/caches/modules-2 ~/.gradle/wrapper key: ${{ needs.build.outputs.cache-key }} restore-keys: | ${{ runner.os }}-gradle- - name: Download build data uses: actions/download-artifact@v8 with: name: dist-build - name: Deploy Maven run: ./gradlew -x test :btrace-dist:publish env: GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_SIGNING_PWD: ${{ secrets.GPG_SIGNING_PWD }} SONATYPE_USER: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} cleanup: runs-on: ubuntu-latest needs: publish steps: - name: Cleanup temporary artifacts uses: geekyeggo/delete-artifact@v6 with: name: dist-build ================================================ FILE: .github/workflows/release.yml ================================================ # BTrace Release Workflow # # This workflow handles the complete release process: # 1. Validates inputs and prerequisites # 2. Runs full test suite (unit + integration tests) # 3. Creates release branch and tag # 4. Stages artifacts to Maven Central (requires manual release) # 5. Waits for Maven Central availability (up to 30 minutes) # 6. Creates GitHub release with artifacts # 7. Updates SDKMan # 8. Updates version numbers for next development cycle # 9. Manages milestones # # IMPORTANT: Maven artifacts are STAGED, not released automatically. # After staging, go to https://central.sonatype.com/publishing/deployments # to review and release. The workflow will wait up to 30 minutes. # # Triggered by scripts/release.sh or manually via workflow_dispatch name: Release on: workflow_dispatch: inputs: release_type: description: 'Release type' required: true type: choice options: - major - minor - patch release_version: description: 'Release version (e.g., 2.3.0)' required: true type: string commit_sha: description: 'Source commit SHA' required: true type: string release_branch: description: 'Release branch (e.g., release/2.3._)' required: true type: string next_snapshot: description: 'Next snapshot version (e.g., 2.4.0-SNAPSHOT)' required: true type: string dry_run: description: 'Dry run (skip publishing and tagging)' required: false type: boolean default: false defaults: run: shell: bash env: RELEASE_VERSION: ${{ inputs.release_version }} RELEASE_TAG: v${{ inputs.release_version }} RC_TAG: v${{ inputs.release_version }}_RC jobs: # ============================================================ # Job 1: Validate inputs and prerequisites # ============================================================ validate: name: Validate Release runs-on: ubuntu-latest outputs: branch_exists: ${{ steps.check-branch.outputs.exists }} create_branch: ${{ steps.check-branch.outputs.create }} steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: Validate inputs run: | echo "Validating release inputs..." echo " Release Type: ${{ inputs.release_type }}" echo " Release Version: ${{ inputs.release_version }}" echo " Commit SHA: ${{ inputs.commit_sha }}" echo " Release Branch: ${{ inputs.release_branch }}" echo " Next Snapshot: ${{ inputs.next_snapshot }}" echo " Dry Run: ${{ inputs.dry_run }}" # Validate version format (X.Y.Z) if [[ ! "${{ inputs.release_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::Invalid version format. Expected X.Y.Z" exit 1 fi # Validate release type if [[ ! "${{ inputs.release_type }}" =~ ^(major|minor|patch)$ ]]; then echo "::error::Invalid release type. Expected major, minor, or patch" exit 1 fi # Validate commit exists if ! git cat-file -e "${{ inputs.commit_sha }}^{commit}" 2>/dev/null; then echo "::error::Commit ${{ inputs.commit_sha }} does not exist" exit 1 fi echo "All inputs validated successfully" - name: Check if tag exists run: | if git rev-parse "v${{ inputs.release_version }}" >/dev/null 2>&1; then echo "::error::Tag v${{ inputs.release_version }} already exists" exit 1 fi if git ls-remote --tags origin "refs/tags/v${{ inputs.release_version }}" | grep -q .; then echo "::error::Tag v${{ inputs.release_version }} already exists on remote" exit 1 fi # Check for leftover RC tag if git rev-parse "${RC_TAG}" >/dev/null 2>&1; then echo "::error::RC tag ${RC_TAG} already exists locally. Clean up with: git tag -d ${RC_TAG}" exit 1 fi if git ls-remote --tags origin "refs/tags/${RC_TAG}" | grep -q .; then echo "::error::RC tag ${RC_TAG} already exists on remote. Clean up with: git push origin :refs/tags/${RC_TAG}" exit 1 fi echo "Tag does not exist - OK" - name: Check if release branch exists id: check-branch run: | BRANCH="${{ inputs.release_branch }}" if git show-ref --verify --quiet "refs/remotes/origin/${BRANCH}"; then echo "Release branch ${BRANCH} already exists" echo "exists=true" >> $GITHUB_OUTPUT echo "create=false" >> $GITHUB_OUTPUT else echo "Release branch ${BRANCH} will be created" echo "exists=false" >> $GITHUB_OUTPUT echo "create=true" >> $GITHUB_OUTPUT fi # ============================================================ # Job 2: Build and run unit tests # ============================================================ build-and-test: name: Build and Test needs: validate runs-on: ubuntu-latest outputs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.commit_sha }} - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up Java if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Generate cache key id: cache-key run: | key="${{ runner.os }}-gradle-release-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}" echo "key=${key}" >> $GITHUB_OUTPUT - name: Cache Gradle uses: actions/cache@v5 with: path: | ~/.gradle/caches/modules-2 ~/.gradle/wrapper key: ${{ steps.cache-key.outputs.key }} restore-keys: | ${{ runner.os }}-gradle- - name: Build run: ./gradlew --no-daemon --parallel --build-cache build - name: Upload dist build data uses: actions/upload-artifact@v7 with: name: dist-build retention-days: 1 path: | btrace-dist/build btrace-instr/build/classes/traces - name: Upload test trace data uses: actions/upload-artifact@v7 with: name: test-trace retention-days: 1 path: | btrace-instr/build/classes/traces - name: Archive test reports if: always() uses: actions/upload-artifact@v7 with: name: unit-test-reports path: | **/reports/**/* # ============================================================ # Job 3: Integration tests on multiple JDK versions # ============================================================ integration-tests: name: Integration Tests (JDK ${{ matrix.java }}) needs: build-and-test runs-on: ubuntu-latest timeout-minutes: 15 strategy: fail-fast: false matrix: java: [8.0.482-tem, 11.0.30-tem, 17.0.18-tem, 21.0.10-tem] env: TEST_JAVA_HOME: "/home/runner/.sdkman/candidates/java/${{ matrix.java }}" steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.commit_sha }} - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Prepare OS run: | sudo apt-get update sudo apt-get install -y curl zip unzip - name: Prepare JDK ${{ matrix.java }} run: | curl -s "https://get.sdkman.io" | bash source "$HOME/.sdkman/bin/sdkman-init.sh" echo 'n' | sdk install java ${{ matrix.java }} which java echo 'y' | sdk install java 11.0.30-tem - name: Cache Gradle uses: actions/cache@v5 with: path: | ~/.gradle/caches/modules-2 ~/.gradle/wrapper key: ${{ needs.build-and-test.outputs.cache-key }} restore-keys: | ${{ runner.os }}-gradle- - name: Download build data uses: actions/download-artifact@v8 with: name: dist-build path: btrace-dist/build - name: Download test trace data uses: actions/download-artifact@v8 with: name: test-trace path: btrace-instr/build/classes/traces - name: Build btrace-instr jar run: | ./gradlew --no-daemon --parallel --build-cache :btrace-instr:jar - name: Run integration tests run: | ./gradlew --no-daemon --parallel --build-cache -Pintegration :integration-tests:test - name: Integration test reports if: always() uses: actions/upload-artifact@v7 with: name: integration-test-reports-${{ matrix.java }} path: | integration-tests/build/reports/**/* # ============================================================ # Job 4: Prepare release (create branch, update version, tag) # ============================================================ prepare-release: name: Prepare Release needs: [validate, integration-tests] runs-on: ubuntu-latest outputs: release_sha: ${{ steps.commit.outputs.sha }} steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Configure Git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Create or checkout release branch run: | BRANCH="${{ inputs.release_branch }}" if git show-ref --verify --quiet "refs/remotes/origin/${BRANCH}"; then echo "Checking out existing branch ${BRANCH}" git checkout -b "${BRANCH}" "origin/${BRANCH}" else echo "Creating new branch ${BRANCH}" git checkout -b "${BRANCH}" fi - name: Update version in build.gradle run: | VERSION="${{ inputs.release_version }}" sed -i "s/version = '.*'/version = '${VERSION}'/" build.gradle echo "Updated version to ${VERSION}" grep "version = " build.gradle - name: Commit version change id: commit run: | git add build.gradle git commit -m "Release ${RELEASE_TAG}" SHA=$(git rev-parse HEAD) echo "sha=${SHA}" >> $GITHUB_OUTPUT echo "Release commit: ${SHA}" - name: Create RC tag if: ${{ inputs.dry_run != true }} run: | git tag -a "${RC_TAG}" -m "Release candidate ${RELEASE_TAG}" echo "Created RC tag ${RC_TAG}" - name: Push branch and RC tag if: ${{ inputs.dry_run != true }} run: | git push origin "${{ inputs.release_branch }}" git push origin "${RC_TAG}" echo "Pushed branch and RC tag" - name: Dry run summary if: ${{ inputs.dry_run == true }} run: | echo "::warning::DRY RUN - Branch and tag NOT pushed" echo "Would have pushed:" echo " - Branch: ${{ inputs.release_branch }}" echo " - RC Tag: ${RC_TAG}" # ============================================================ # Job 5: Stage to Maven Central (does NOT release) # ============================================================ stage-maven: name: Stage to Maven Central needs: [prepare-release, build-and-test] runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout RC tag uses: actions/checkout@v6 with: ref: ${{ env.RC_TAG }} - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up Java if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Cache Gradle uses: actions/cache@v5 with: path: | ~/.gradle/caches/modules-2 ~/.gradle/wrapper key: ${{ needs.build-and-test.outputs.cache-key }} restore-keys: | ${{ runner.os }}-gradle- - name: Stage to Maven Central env: GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_SIGNING_PWD: ${{ secrets.GPG_SIGNING_PWD }} SONATYPE_USER: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} run: | echo "Staging artifacts to Central Portal..." echo "After this job completes, go to https://central.sonatype.com/publishing/deployments" echo "to review and release the staged artifacts." ./gradlew :btrace-dist:publishAllPublicationsToSonatypeRepository --no-daemon - name: Staging complete run: | echo "==============================================" echo "ARTIFACTS STAGED - MANUAL RELEASE REQUIRED" echo "==============================================" echo "" echo "Staged artifacts:" echo " - io.btrace:btrace-agent:${{ inputs.release_version }}" echo " - io.btrace:btrace-client:${{ inputs.release_version }}" echo " - io.btrace:btrace-boot:${{ inputs.release_version }}" echo "" echo "Next steps:" echo " 1. Go to https://central.sonatype.com/publishing/deployments" echo " 2. Review the staged repository" echo " 3. Click 'Publish' to release to Maven Central" echo "" echo "The workflow will wait up to 30 minutes for artifacts to appear." echo "If you don't want to proceed, let the workflow timeout or cancel it." # ============================================================ # Job 5b: Wait for Maven Central availability # ============================================================ wait-for-maven: name: Wait for Maven Central needs: stage-maven runs-on: ubuntu-latest timeout-minutes: 35 steps: - name: Wait for artifacts on Maven Central run: | VERSION="${{ inputs.release_version }}" ARTIFACT_URL="https://repo1.maven.org/maven2/io/btrace/btrace-client/${VERSION}/btrace-client-${VERSION}.pom" echo "Waiting for Maven Central to have: ${ARTIFACT_URL}" echo "This requires manual release from Central Portal." echo "" echo "Go to: https://central.sonatype.com/publishing/deployments" echo "" MAX_ATTEMPTS=60 # 30 minutes with 30s intervals ATTEMPT=0 while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do ATTEMPT=$((ATTEMPT + 1)) echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}: Checking Maven Central..." HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${ARTIFACT_URL}") if [ "$HTTP_STATUS" = "200" ]; then echo "" echo "SUCCESS: Artifacts are available on Maven Central!" echo "Proceeding with GitHub release and SDKMan announcement..." exit 0 fi echo " Status: ${HTTP_STATUS} - Not available yet. Waiting 30s..." sleep 30 done echo "" echo "TIMEOUT: Artifacts did not appear on Maven Central within 30 minutes." echo "The release was NOT finalized." echo "" echo "To complete the release manually:" echo " 1. Release artifacts via Central Portal" echo " 2. Create GitHub release manually" echo " 3. Run: ./gradlew :btrace-dist:sdkMinorRelease" exit 1 # ============================================================ # Job 5c: Update JBang catalog # ============================================================ update-jbang-catalog: name: Update JBang Catalog needs: [prepare-release, wait-for-maven] runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout jbang-catalog continue-on-error: true uses: actions/checkout@v6 with: repository: btraceio/jbang-catalog token: ${{ secrets.JBANG_CATALOG_PAT || secrets.GITHUB_TOKEN }} path: catalog - name: Update catalog files continue-on-error: true run: | VERSION="${{ inputs.release_version }}" if [ ! -d catalog ]; then echo "⚠️ Catalog checkout failed - skipping update" echo "Add JBANG_CATALOG_PAT secret to enable automatic catalog updates" exit 0 fi cd catalog # Update btrace.java dependency sed -i.bak "s|//DEPS io.btrace:btrace-client:.*|//DEPS io.btrace:btrace-client:${VERSION}|g" btrace.java # Update btrace-latest.java dependency sed -i.bak "s|//DEPS io.btrace:btrace-client:.*|//DEPS io.btrace:btrace-client:${VERSION}|g" btrace-latest.java # Clean up backup files rm -f *.bak # Show changes git diff - name: Commit and push catalog updates continue-on-error: true run: | VERSION="${{ inputs.release_version }}" if [ ! -d catalog ]; then echo "⚠️ Catalog checkout failed - skipping update" echo "Add JBANG_CATALOG_PAT secret to enable automatic catalog updates" exit 0 fi cd catalog git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add btrace.java btrace-latest.java if git commit -m "Update btrace to version ${VERSION}"; then git push || echo "⚠️ Push failed - update catalog manually at https://github.com/btraceio/jbang-catalog" fi # ============================================================ # Job 5d: Finalize release tag (RC -> final) # ============================================================ finalize-tag: name: Finalize Release Tag needs: [prepare-release, wait-for-maven] runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout at release SHA uses: actions/checkout@v6 with: ref: ${{ needs.prepare-release.outputs.release_sha }} fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Fetch tags run: git fetch origin --tags - name: Configure Git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Create final tag run: | git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG}" echo "Created final tag ${RELEASE_TAG}" - name: Push final tag run: | git push origin "${RELEASE_TAG}" echo "Pushed final tag ${RELEASE_TAG}" - name: Delete RC tag run: | git tag -d "${RC_TAG}" || true git push origin ":refs/tags/${RC_TAG}" || true echo "Deleted RC tag ${RC_TAG}" # ============================================================ # Job 6: Build distribution packages # ============================================================ build-distributions: name: Build Distributions needs: prepare-release runs-on: ubuntu-latest steps: - name: Checkout release commit uses: actions/checkout@v6 with: ref: ${{ inputs.dry_run == true && inputs.commit_sha || env.RC_TAG }} - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up Java if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Update version for dry run if: ${{ inputs.dry_run == true }} run: | VERSION="${{ inputs.release_version }}" sed -i "s/project.version = '.*'/project.version = '${VERSION}'/" common.gradle - name: Build distribution packages run: | ./gradlew :btrace-dist:build --no-daemon - name: List artifacts run: | echo "Distribution artifacts:" ls -la btrace-dist/build/distributions/ - name: Upload distribution artifacts uses: actions/upload-artifact@v7 with: name: release-distributions retention-days: 7 path: | btrace-dist/build/distributions/btrace-v${{ inputs.release_version }}-bin.tar.gz btrace-dist/build/distributions/btrace-v${{ inputs.release_version }}-bin.zip btrace-dist/build/distributions/btrace-v${{ inputs.release_version }}-sdkman-bin.zip btrace-dist/build/distributions/*.deb btrace-dist/build/distributions/*.rpm # ============================================================ # Job 7: Create GitHub Release # ============================================================ create-github-release: name: Create GitHub Release needs: [build-distributions, finalize-tag] runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} fetch-depth: 0 - name: Download distribution artifacts uses: actions/download-artifact@v8 with: name: release-distributions path: distributions - name: Download Maven artifact continue-on-error: true run: | VERSION="${{ inputs.release_version }}" BASE_URL="https://repo1.maven.org/maven2/io/btrace/btrace/${VERSION}" mkdir -p maven-artifacts MAX_ATTEMPTS=40 for i in $(seq 1 $MAX_ATTEMPTS); do if curl -fSL "${BASE_URL}/btrace-${VERSION}.jar" \ -o maven-artifacts/btrace-${VERSION}.jar; then echo "Downloaded btrace-${VERSION}.jar" ls -la maven-artifacts/ exit 0 fi echo "Attempt ${i}/${MAX_ATTEMPTS} failed, retrying in 30s..." sleep 30 done echo "::warning::Failed to download Maven JAR after ${MAX_ATTEMPTS} attempts (~20 minutes). GitHub release will proceed without it." - name: Check for no-release-notes label id: check-label run: | # Check if this release should skip auto-generated notes SKIP_NOTES="false" # This would check PRs merged since last release for the label # For now, always generate release notes echo "skip_notes=${SKIP_NOTES}" >> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ env.RELEASE_TAG }} name: BTrace ${{ inputs.release_version }} draft: false prerelease: false generate_release_notes: true files: | distributions/btrace-v${{ inputs.release_version }}-bin.tar.gz distributions/btrace-v${{ inputs.release_version }}-bin.zip distributions/btrace-v${{ inputs.release_version }}-sdkman-bin.zip distributions/*.deb distributions/*.rpm maven-artifacts/* # ============================================================ # Job 8: Update SDKMan # ============================================================ update-sdkman: name: Update SDKMan needs: create-github-release runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout release tag uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up Java if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Announce to SDKMan env: SDKMAN_API_KEY: ${{ secrets.SDKMAN_KEY }} SDKMAN_API_TOKEN: ${{ secrets.SDKMAN_TOKEN }} run: | # Use sdkMajorRelease for major releases, sdkMinorRelease otherwise if [[ "${{ inputs.release_type }}" == "major" ]]; then ./gradlew :btrace-dist:sdkMajorRelease --no-daemon else ./gradlew :btrace-dist:sdkMinorRelease --no-daemon fi # ============================================================ # Job 9: Update develop branch (major/minor only) # ============================================================ update-develop: name: Update Develop Branch needs: prepare-release runs-on: ubuntu-latest if: ${{ inputs.release_type != 'patch' && inputs.dry_run != true }} steps: - name: Checkout develop uses: actions/checkout@v6 with: ref: develop fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Configure Git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Update version in develop run: | NEXT_VERSION="${{ inputs.next_snapshot }}" sed -i "s/project.version = '.*'/project.version = '${NEXT_VERSION}'/" common.gradle echo "Updated develop to ${NEXT_VERSION}" grep "project.version" common.gradle - name: Commit and push run: | git add common.gradle git commit -m "Bump version to ${{ inputs.next_snapshot }} after ${RELEASE_TAG} release" git push origin develop # ============================================================ # Job 10: Update release branch to next patch snapshot # ============================================================ update-release-branch: name: Update Release Branch needs: [prepare-release, create-github-release] runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout release branch uses: actions/checkout@v6 with: ref: ${{ inputs.release_branch }} fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Configure Git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Calculate next patch snapshot id: next-version run: | CURRENT="${{ inputs.release_version }}" MAJOR=$(echo $CURRENT | cut -d. -f1) MINOR=$(echo $CURRENT | cut -d. -f2) PATCH=$(echo $CURRENT | cut -d. -f3) NEXT_PATCH=$((PATCH + 1)) NEXT_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}-SNAPSHOT" echo "version=${NEXT_VERSION}" >> $GITHUB_OUTPUT echo "Next patch version: ${NEXT_VERSION}" - name: Update version in release branch run: | NEXT_VERSION="${{ steps.next-version.outputs.version }}" sed -i "s/project.version = '.*'/project.version = '${NEXT_VERSION}'/" common.gradle echo "Updated release branch to ${NEXT_VERSION}" grep "project.version" common.gradle - name: Commit and push run: | git add common.gradle git commit -m "Bump version to ${{ steps.next-version.outputs.version }} for next patch release" git push origin ${{ inputs.release_branch }} # ============================================================ # Job 11: Manage milestones # ============================================================ update-milestones: name: Update Milestones needs: create-github-release runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Create and close milestone env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | MILESTONE_TITLE="${{ inputs.release_version }}" # Check if milestone exists MILESTONE=$(gh api repos/${{ github.repository }}/milestones --jq ".[] | select(.title == \"${MILESTONE_TITLE}\")") if [[ -z "${MILESTONE}" ]]; then echo "Creating milestone ${MILESTONE_TITLE}..." gh api repos/${{ github.repository }}/milestones \ -X POST \ -f title="${MILESTONE_TITLE}" \ -f state="closed" \ -f description="Released as ${RELEASE_TAG}" else MILESTONE_NUMBER=$(echo "${MILESTONE}" | jq -r '.number') echo "Closing existing milestone ${MILESTONE_TITLE} (#${MILESTONE_NUMBER})..." gh api repos/${{ github.repository }}/milestones/${MILESTONE_NUMBER} \ -X PATCH \ -f state="closed" fi - name: Associate merged PRs with milestone env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Get the milestone number MILESTONE_TITLE="${{ inputs.release_version }}" MILESTONE_NUMBER=$(gh api repos/${{ github.repository }}/milestones --jq ".[] | select(.title == \"${MILESTONE_TITLE}\") | .number") if [[ -z "${MILESTONE_NUMBER}" ]]; then echo "Warning: Could not find milestone ${MILESTONE_TITLE}" exit 0 fi # Find the previous release tag PREV_TAG=$(git describe --tags --abbrev=0 "${RELEASE_TAG}^" 2>/dev/null || echo "") if [[ -z "${PREV_TAG}" ]]; then echo "No previous tag found, skipping PR association" exit 0 fi echo "Finding PRs merged between ${PREV_TAG} and ${RELEASE_TAG}..." # Get commits between tags COMMITS=$(git log "${PREV_TAG}..${RELEASE_TAG}" --pretty=format:"%H" 2>/dev/null || echo "") for COMMIT in ${COMMITS}; do # Find PR associated with this commit PR_NUMBER=$(gh api repos/${{ github.repository }}/commits/${COMMIT}/pulls --jq '.[0].number' 2>/dev/null || echo "") if [[ -n "${PR_NUMBER}" && "${PR_NUMBER}" != "null" ]]; then echo "Associating PR #${PR_NUMBER} with milestone ${MILESTONE_TITLE}..." gh api repos/${{ github.repository }}/issues/${PR_NUMBER} \ -X PATCH \ -f milestone="${MILESTONE_NUMBER}" 2>/dev/null || true fi done # ============================================================ # Job 12: Release summary # ============================================================ summary: name: Release Summary needs: [create-github-release, update-sdkman, update-milestones, update-jbang-catalog, finalize-tag] runs-on: ubuntu-latest if: always() steps: - name: Generate summary run: | echo "# Release Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Release Details" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| Version | ${{ inputs.release_version }} |" >> $GITHUB_STEP_SUMMARY echo "| Tag | ${RELEASE_TAG} |" >> $GITHUB_STEP_SUMMARY echo "| Release Type | ${{ inputs.release_type }} |" >> $GITHUB_STEP_SUMMARY echo "| Source Commit | ${{ inputs.commit_sha }} |" >> $GITHUB_STEP_SUMMARY echo "| Release Branch | ${{ inputs.release_branch }} |" >> $GITHUB_STEP_SUMMARY echo "| Dry Run | ${{ inputs.dry_run }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [[ "${{ inputs.dry_run }}" != "true" ]]; then echo "## Links" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/${RELEASE_TAG})" >> $GITHUB_STEP_SUMMARY echo "- [Maven Central](https://central.sonatype.com/search?q=io.btrace&version=${{ inputs.release_version }})" >> $GITHUB_STEP_SUMMARY echo "- [SDKMan](https://sdkman.io/sdks#btrace)" >> $GITHUB_STEP_SUMMARY echo "- JBang: \`jbang btrace@${{ inputs.release_version }}\` (uses Maven Central)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Maven Coordinates" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo '```xml' >> $GITHUB_STEP_SUMMARY echo '' >> $GITHUB_STEP_SUMMARY echo ' io.btrace' >> $GITHUB_STEP_SUMMARY echo ' btrace-client' >> $GITHUB_STEP_SUMMARY echo " ${{ inputs.release_version }}" >> $GITHUB_STEP_SUMMARY echo '' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY else echo "## Dry Run Mode" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "> **Note:** This was a dry run. No artifacts were published or tags created." >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY echo "## Job Status" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY echo "| Validate | ${{ needs.validate.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Build & Test | ${{ needs.build-and-test.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Integration Tests | ${{ needs.integration-tests.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Prepare Release | ${{ needs.prepare-release.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Stage Maven | ${{ needs.stage-maven.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Wait for Maven | ${{ needs.wait-for-maven.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Finalize Tag | ${{ needs.finalize-tag.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| GitHub Release | ${{ needs.create-github-release.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| SDKMan | ${{ needs.update-sdkman.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| JBang Catalog | ${{ needs.update-jbang-catalog.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Milestones | ${{ needs.update-milestones.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 13: Cleanup # ============================================================ cleanup: name: Cleanup Artifacts needs: [summary] runs-on: ubuntu-latest if: always() steps: - name: Cleanup temporary artifacts uses: geekyeggo/delete-artifact@v6 with: name: | dist-build test-trace ================================================ FILE: .github/workflows/stale.yml ================================================ name: Mark stale issues and pull requests on: schedule: - cron: "0 0 * * *" jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'Stale issue message' stale-pr-message: 'Stale pull request message' stale-issue-label: 'no-issue-activity' stale-pr-label: 'no-pr-activity' ================================================ FILE: .github/workflows/update-jdk-versions.yml ================================================ name: Update JDK Test Versions on: schedule: - cron: '0 6 * * 1' workflow_dispatch: permissions: contents: write pull-requests: write jobs: update-versions: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 with: ref: develop token: ${{ secrets.PAT_WORKFLOW }} - name: Check for version updates id: update run: | chmod +x scripts/update-jdk-versions.sh if changes=$(scripts/update-jdk-versions.sh); then echo "changed=true" >> "$GITHUB_OUTPUT" { echo "body<> "$GITHUB_OUTPUT" else echo "changed=false" >> "$GITHUB_OUTPUT" fi - name: Create Pull Request if: steps.update.outputs.changed == 'true' uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.PAT_WORKFLOW }} branch: automation/update-jdk-versions base: develop title: 'chore: update JDK test versions' draft: true labels: automation commit-message: 'chore: update JDK test versions' body: | Automated JDK version update detected by SDKMan API. ### Changes ${{ steps.update.outputs.body }} ================================================ FILE: .github/workflows/v2-protocol-tests.yml ================================================ # Workflow for testing BTrace Binary Protocol v2 # This workflow runs comprehensive tests for the v2 protocol implementation # including unit tests, JMH benchmarks, and protocol negotiation tests name: V2 Protocol Tests on: # Run on PRs when labeled with 'test:v2-protocol' pull_request: types: [ labeled ] # Run weekly on Sunday at 2 AM UTC schedule: - cron: '0 2 * * 0' # Allow manual trigger workflow_dispatch: defaults: run: shell: bash jobs: unit-tests: name: V2 Protocol Unit Tests runs-on: ubuntu-latest # Only run if triggered by schedule, manual dispatch, or PR with 'test:v2-protocol' label if: | github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test:v2-protocol')) strategy: matrix: java: [ 11, 17, 21 ] steps: - name: Checkout uses: actions/checkout@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-${{ matrix.java }} - name: Set up JDK ${{ matrix.java }} if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: ${{ matrix.java }} distribution: temurin - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Cache Gradle packages uses: actions/cache@v5 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- - name: Run V2 Protocol Tests run: | ./gradlew :btrace-core:test --tests "org.openjdk.btrace.core.comm.v2.*" \ --tests "org.openjdk.btrace.core.comm.Protocol*" \ --tests "org.openjdk.btrace.core.comm.WireProtocol*" \ --rerun-tasks - name: Upload Test Reports if: always() uses: actions/upload-artifact@v7 with: name: test-reports-java-${{ matrix.java }} retention-days: 7 path: | btrace-core/build/reports/tests/** btrace-core/build/test-results/** protocol-negotiation-tests: name: Protocol Negotiation Tests runs-on: ubuntu-latest if: | github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test:v2-protocol')) steps: - name: Checkout uses: actions/checkout@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up JDK 11 if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Run Protocol Negotiation Tests run: | ./gradlew :btrace-core:test --tests "org.openjdk.btrace.core.comm.ProtocolNegotiatorTest" \ --tests "org.openjdk.btrace.core.comm.ProtocolConfigTest" \ --tests "org.openjdk.btrace.core.comm.WireProtocolTest" \ --rerun-tasks - name: Verify Protocol Version Detection run: | echo "Testing V1 protocol detection..." ./gradlew :btrace-core:test --tests "*ProtocolNegotiatorTest.testNegotiateV1*" echo "Testing V2 protocol detection..." ./gradlew :btrace-core:test --tests "*ProtocolNegotiatorTest.testNegotiateV2*" - name: Upload Negotiation Test Reports if: always() uses: actions/upload-artifact@v7 with: name: negotiation-test-reports retention-days: 7 path: | btrace-core/build/reports/tests/** btrace-core/build/test-results/** edge-case-tests: name: Edge Case and Boundary Tests runs-on: ubuntu-latest if: | github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test:v2-protocol')) steps: - name: Checkout uses: actions/checkout@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up JDK 11 if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Run Edge Case Tests run: | ./gradlew :btrace-core:test --tests "org.openjdk.btrace.core.comm.v2.BinaryProtocolEdgeCasesTest" \ --rerun-tasks - name: Test Large Messages run: | ./gradlew :btrace-core:test --tests "*testVeryLargeMessage" \ --tests "*testLargeBytecodeArray" \ --tests "*testMapWith1000Entries" - name: Test Compression run: | ./gradlew :btrace-core:test --tests "*testCompressionThreshold" \ --tests "*testHighlyCompressibleMessage" \ --tests "*testCompressionJustAboveThreshold" - name: Upload Edge Case Reports if: always() uses: actions/upload-artifact@v7 with: name: edge-case-reports retention-days: 7 path: | btrace-core/build/reports/tests/** jmh-benchmarks: name: JMH Performance Benchmarks runs-on: ubuntu-latest if: github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[benchmark]') steps: - name: Checkout uses: actions/checkout@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up JDK 11 if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Run Quick JMH Benchmarks (warmup=1, iterations=2, fork=1) run: | ./gradlew :btrace-core:jmh \ -PjmhInclude=".*Serialize.*" \ -Pjmh.warmupIterations=1 \ -Pjmh.iterations=2 \ -Pjmh.fork=1 - name: Upload JMH Results if: always() uses: actions/upload-artifact@v7 with: name: jmh-results retention-days: 30 path: | btrace-core/build/reports/jmh/** btrace-core/build/jmh-results/** protocol-compatibility: name: Protocol Compatibility Matrix runs-on: ubuntu-latest if: | github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test:v2-protocol')) strategy: matrix: scenario: - name: "V2-to-V2" client: "v2" agent: "v2" - name: "V1-to-V1" client: "v1" agent: "v1" - name: "V2-to-V1" client: "v2" agent: "v1" - name: "V1-to-V2" client: "v1" agent: "v2" steps: - name: Checkout uses: actions/checkout@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up JDK 11 if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Test ${{ matrix.scenario.name }} Compatibility run: | echo "Testing compatibility: Client=${{ matrix.scenario.client }} Agent=${{ matrix.scenario.agent }}" ./gradlew :btrace-core:test --tests "org.openjdk.btrace.core.comm.WireProtocolTest" \ --rerun-tasks \ -Dbtrace.comm.protocol=${{ matrix.scenario.client }} - name: Upload Compatibility Reports if: always() uses: actions/upload-artifact@v7 with: name: compatibility-${{ matrix.scenario.name }} retention-days: 7 path: | btrace-core/build/reports/tests/** test-summary: name: Test Summary runs-on: ubuntu-latest needs: [unit-tests, protocol-negotiation-tests, edge-case-tests, protocol-compatibility] if: always() steps: - name: Download All Test Reports uses: actions/download-artifact@v8 with: path: test-reports - name: Generate Test Summary run: | echo "# V2 Protocol Test Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Test Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Count test results total_tests=0 passed_tests=0 failed_tests=0 for report in test-reports/*/test-results/test/*.xml; do if [ -f "$report" ]; then tests=$(grep -oP 'tests="\K[0-9]+' "$report" || echo "0") failures=$(grep -oP 'failures="\K[0-9]+' "$report" || echo "0") total_tests=$((total_tests + tests)) failed_tests=$((failed_tests + failures)) fi done passed_tests=$((total_tests - failed_tests)) echo "- **Total Tests:** $total_tests" >> $GITHUB_STEP_SUMMARY echo "- **Passed:** ✅ $passed_tests" >> $GITHUB_STEP_SUMMARY echo "- **Failed:** ❌ $failed_tests" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [ $failed_tests -eq 0 ]; then echo "✅ All V2 protocol tests passed!" >> $GITHUB_STEP_SUMMARY exit 0 else echo "❌ Some tests failed. Please review the reports." >> $GITHUB_STEP_SUMMARY exit 1 fi code-coverage: name: Code Coverage runs-on: ubuntu-latest if: | github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test:v2-protocol')) steps: - name: Checkout uses: actions/checkout@v6 - name: Cache Java binaries id: cache-java uses: actions/cache@v5 with: path: ${{ runner.tool_cache }}/Java_* key: java-${{ runner.os }}-temurin-11 - name: Set up JDK 11 if: steps.cache-java.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Run Tests with Coverage run: | ./gradlew :btrace-core:test jacocoTestReport - name: Upload Coverage Reports uses: actions/upload-artifact@v7 with: name: coverage-reports retention-days: 30 path: | btrace-core/build/reports/jacoco/** btrace-core/build/jacoco/** - name: Generate Coverage Summary run: | if [ -f "btrace-core/build/reports/jacoco/test/html/index.html" ]; then echo "## Code Coverage" >> $GITHUB_STEP_SUMMARY echo "Coverage report generated successfully" >> $GITHUB_STEP_SUMMARY echo "See artifacts for detailed coverage data" >> $GITHUB_STEP_SUMMARY fi ================================================ FILE: .gitignore ================================================ *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear # Un-ignore specific files !btrace-instr/src/test/resources/packed/test-pack.jar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* build dist make/netbeans/nbproject/private TEST-* make/junit* make/private.properties btrace-benchmark/target btrace-benchmark/build benchmark/*.iml junit* /btrace-statsd/target/ *.iml .gradle .gradle-user .idea !/lib/btrace-asm-*.jar !/lib/btrace-jctools-core-*.jar !/lib/btrace-hppcrt-*.jar !/test-lib/*.jar CHANGELOG.md gradle.properties /.nb-gradle/ /.nb-gradle-properties .github_changelog_generator **/out/* gradle-wrapper.properties /.java.versions ================================================ FILE: .muse/advisor.md ================================================ # BTrace Project Advisor ## Repository: btraceio/btrace ### Branch model - **`develop`** is the sole integration branch — ALL pull requests must target `develop`, never `master`. - `master` does not exist on the remote. - Release tags are cut from `develop` after a stabilization window. ### Build commands ```bash # Full build with dist packages ./gradlew :btrace-dist:build # Module-specific build (faster) ./gradlew ::build -x test # All tests ./gradlew test # Instrumentation tests only (fastest for instr changes) ./gradlew :btrace-instr:test # Regenerate golden files after intentional bytecode changes ./gradlew :btrace-instr:test -PupdateTestData # Integration tests (requires Docker + full dist build first) ./gradlew :btrace-dist:build ./gradlew :integration-tests:test -Pintegration # Format check / auto-format (Google Java Format via Spotless) ./gradlew spotlessCheck ./gradlew spotlessApply ``` ### Module layout | Module | Purpose | |---|---| | `btrace-core` | Annotations, wire protocol, shared types — must stay Java 8 compatible | | `btrace-compiler` | Script compilation + safety verification (`Verifier.java`) | | `btrace-instr` | ASM-based bytecode instrumentation; probe factory; `HandlerRepositoryImpl` | | `btrace-runtime` | Multi-version runtime impls (base/java9/java11/java15); `IndyDispatcher` | | `btrace-agent` | Java agent entry point; `RemoteClient`, `FileClient` | | `btrace-client` | CLI client tool | | `btrace-dist` | Distribution packaging | | `integration-tests` | End-to-end Docker-based tests | | `benchmarks` | JMH benchmarks | ### Key architectural constraints - `btrace-core` and `btrace-runtime` (src/main/java/) must compile at Java 8 source/target level. - Multi-version runtime jars: `src/main/java9/`, `src/main/java11/`, `src/main/java15/` — each compiled at its respective release level. - `btrace-boot.jar` is on the bootstrap classpath; classes in `btrace-runtime` that are referenced from INVOKEDYNAMIC bootstrap methods must be bootstrap-loadable. - All probe script classes are defined in the bootstrap CL (via `Unsafe.defineClass` with `mustBeBootstrap=true` when `isTransforming()`). - BTrace verifier enforces that probe handler methods are `public static void` — `publicLookup().findStatic()` is therefore sufficient. - Golden files for instrumentation tests live in `btrace-instr/src/test/resources/instrumentorTestData/dynamic/`. Run with `-PupdateTestData` to regenerate after intentional bytecode changes. ### PR checklist Before opening a PR, verify: 1. Branch targets **`develop`** (never `master`) 2. `./gradlew spotlessApply` applied 3. `./gradlew :btrace-instr:test` passes 4. Golden files regenerated if instrumented bytecode changed (`-PupdateTestData`) 5. If touching runtime multi-version code: test on JDK 11 and JDK 17+ ================================================ FILE: AGENTS.md ================================================ # Repository Guidelines ## Project Structure & Modules - Root uses Gradle with multiple modules named `btrace-*`. - Core code lives in module directories (for example, `btrace-core`, `btrace-agent`, `btrace-runtime`, `btrace-client`, `btrace-instr`). - Distributions are built from `btrace-dist`. - Integration tests live in `integration-tests`; benchmarks in `benchmarks/*`; docs in `docs/`. ## Architecture Overview - btrace-agent: Attachable Java agent that installs a class transformer and manages script lifecycle (load/unload), output routing, and optional JFR hooks. - btrace-compiler: Verifies and compiles BTrace scripts to bytecode. - btrace-instr: ASM-based instrumentation and weaving utilities used by the agent/compiler. - btrace-runtime: APIs exposed to scripts; provides safe helpers for printing, timers, and data collection. - btrace-client: CLI/attach tooling that sends compiled scripts to the target JVM and streams results. - extensions: API + implementations packaged as BTrace extensions (for example, statsd and metrics under `btrace-extensions/*`). - Flow: client attaches → compiles/sends script → agent loads and instruments target classes → runtime emits events → client displays/exports. ### High-Level Flow ``` +--------------+ attach/send +-------------+ transform +------------------+ | btrace-client| -----------------> | btrace-agent| --------------> | instrumented JVM | +--------------+ +-------------+ +------------------+ ^ | ^ | | events/logs/stdout | | load/unload scripts | | <------------------------------------+ +-------------------------------+ | +--------- optional exporters via services (eg. statsd) --------------------> ``` ### Modules (at a glance) ``` btrace-client -> btrace-agent -> btrace-instr | | v v btrace-runtime extensions (e.g., statsd, utils, metrics) btrace-compiler (validates/compiles scripts) btrace-dist (packages binaries) ``` ## Distribution Architecture: Masked JAR BTrace uses a **single masked JAR** (`btrace.jar`) as its distribution artifact. This JAR contains: ### Structure ``` btrace.jar ├── META-INF/ │ ├── MANIFEST.MF (Main-Class, Premain-Class, Agent-Class → org.openjdk.btrace.boot.Loader) │ ├── btrace/ │ │ ├── agent/*.classdata (agent classes - loaded in agent mode) │ │ ├── client/*.classdata (client classes - loaded in client mode) │ │ └── shared/*.classdata (shared classes - loaded in both modes) ├── org/openjdk/btrace/boot/ (bootstrap classes - visible to JVM) └── org/openjdk/btrace/core/ (core/runtime classes from bootstrap module) ``` ### Class Loading Strategy 1. **Bootstrap Classes** (`.class` files in root): - Loaded by bootstrap classloader - Visible to JVM and all code - Includes: Loader, MaskedClassLoader, MaskedJarUtils - These classes initialize the masked jar system 2. **Agent Classes** (`.classdata` in `META-INF/btrace/agent/`): - Loaded via MaskedClassLoader in agent mode - Isolated from application classes - Includes: btrace-agent, btrace-instr, btrace-runtime, relocated jctools 3. **Client Classes** (`.classdata` in `META-INF/btrace/client/`): - Loaded via MaskedClassLoader in client mode - Includes: btrace-client, btrace-compiler, lanterna UI 4. **Shared Classes** (`.classdata` in `META-INF/btrace/shared/`): - Loaded in both agent and client modes - Includes: communication protocol, annotations, ASM core - Critical for agent-client communication ### Why Masked JAR? - **Single Source of Truth**: One JAR for all use cases (agent, client, standalone) - **Bootstrap Isolation**: Agent/client classes hidden from JVM, preventing conflicts - **No Embedded JARs**: Eliminates nested JAR extraction overhead - **Simplified Build**: Removed redundant uber jar - masked jar handles everything ### Build Process The masked JAR is built in `btrace-dist/build.gradle`: 1. `allClassesShadow` - Creates intermediate shadow jar with all dependencies and relocations 2. `prepareAgentClassdata` - Extracts agent classes, renames `.class` → `.classdata` 3. `prepareClientClassdata` - Extracts client classes, renames `.class` → `.classdata` 4. `prepareSharedClassdata` - Extracts shared classes, renames `.class` → `.classdata` 5. `btraceJar` - Combines bootstrap classes (as `.class`) + masked classes (as `.classdata`) ### Debugging Tips - **ClassNotFoundException in agent mode**: Check if class is in `META-INF/btrace/agent/` (or shared if needed) - **ClassNotFoundException in client mode**: Check if class is in `META-INF/btrace/client/` (or shared if needed) - **NoClassDefFoundError between modes**: Class may need to be in shared section - **Inspect masked JAR**: `unzip -l btrace.jar | grep -E "(\.class|\.classdata)"` - **Check manifest**: `unzip -p btrace.jar META-INF/MANIFEST.MF` ## Launch Modes ``` Launch-time (agent mode): java -javaagent:$BTRACE_HOME/libs/btrace.jar=script=MyTrace.java -jar app.jar |-> Loader.premain() -> loads agent classes from .classdata -> installs transformer Attach-time (client mode): btrace MyTrace.java |-> Loader as Main-Class -> loads client classes from .classdata -> attaches to target JVM |-> Target JVM: Loader.agentmain() -> loads agent classes from .classdata -> instruments Standalone (client mode): java -jar btrace.jar |-> Same as attach-time, Loader delegates to client ``` ## Troubleshooting - Attach disabled: if JVM was started with `-XX:+DisableAttachMechanism`, remove it or relaunch without it. - Permission errors: attach requires same OS user as target JVM; on Linux/macOS avoid sudo mixing; check container/JDK permissions. - Toolchains: ensure `JAVA_HOME` and optional `TEST_JAVA_HOME` point to valid JDKs; for integration tests, build `btrace-dist` first so client/libs exist. ### Masked JAR Troubleshooting - **ClassNotFoundException with .classdata**: MaskedClassLoader can't find class in masked sections. Check: 1. Is the class in the correct section? (agent/client/shared) 2. Was the class relocated? Check package name matches relocated path 3. Did the build complete successfully? Rebuild with `./gradlew clean :btrace-dist:btraceJar` - **Shared classes**: If a class is used by BOTH agent and client (e.g., comm protocol, annotations), it MUST be in the shared section - **Bootstrap vs Masked**: Bootstrap classes (.class) are visible everywhere; masked classes (.classdata) are isolated per-mode - **Build order matters**: `allClassesShadow` must complete before prepare*Classdata tasks run ## Example Script ```java package helloworld; import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.*; import org.openjdk.btrace.core.types.AnyType; @BTrace public class MyTrace { @OnMethod(clazz="extra.HelloWorld", method="/.*/") public static void onAny(@ProbeMethodName String pmn) { println("entered: " + pmn); } } ``` Run with: `btrace MyTrace.java` (see docs/BTraceTutorial.md for steps). ```java // Args capture package helloworld; import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.*; import org.openjdk.btrace.core.types.AnyType; @BTrace public class ArgsTrace { @OnMethod(clazz="extra.HelloWorld", method="/call.*/") public static void onCall(@ProbeMethodName String pmn, AnyType[] args) { println("args for " + pmn); printArray(args); } } ``` ```java // Return value and duration package helloworld; import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.*; import org.openjdk.btrace.core.types.AnyType; @BTrace public class ReturnTrace { @OnMethod(clazz="extra.HelloWorld", method="callC", location=@Location(Kind.RETURN)) public static void onReturn(@Duration long dur, @Return AnyType ret) { println("callC ret=" + str(ret) + ", dur(ns)=" + dur); } } ``` ## Build, Test, and Development ! Do not consume the gradle task logs directly. ! ! Write the output to a file, running through grep to include only relevant information and then read the log file. ! - Full build: `./gradlew build` — compiles all modules and runs unit tests. - Distribution: `./gradlew :btrace-dist:build` — creates ZIP/TGZ/RPM/DEB and an exploded layout under `btrace-dist/build/resources/main`. - Unit tests: `./gradlew test` — JUnit 5, runs per-module tests. - Integration tests: first build dist, then `./gradlew -Pintegration test`. - Requires `JAVA_HOME` and typically `TEST_JAVA_HOME` (e.g., JDK 11). Example: `TEST_JAVA_HOME=$JAVA_11_HOME ./gradlew -Pintegration test`. - Formatting: `./gradlew spotlessApply` (check with `spotlessCheck`). - Coverage: `./gradlew jacocoTestReport` (CI publishes to Codecov). ## Coding Style & Naming - Language: Java. Source/target set to 8; toolchains compile with JDK 11. - Format: Google Java Format via Spotless. Import order enforced; unused imports removed. - Packages under `org.openjdk.btrace.*`. - Module names follow `btrace-` (e.g., `btrace-extensions:btrace-utils`). ## Testing Guidelines - Framework: JUnit Jupiter (JUnit 5). - Unit tests reside under `src/test/java`; name classes with `*Test`. - Integration tests in `integration-tests/src/test/java`; BTrace scripts under `integration-tests/src/test/btrace`. - For integration runs, ensure `btrace-dist/build/resources/main/v/libs/btrace.jar` exists (created by the dist build). - The masked JAR is used for all integration tests - both agent and client modes use the same artifact. ## Commit & Pull Request Guidelines - Commit style: Conventional Commits (e.g., `feat(core): add probe`, `fix(instr): handle null arg`). - PRs must be from signers of the Oracle Contributor Agreement (OCA) — see README. - PR checklist: - Clear description and rationale; link related issues. - Tests updated/added; CI green across unit and integration suites. - Formatting passes (`spotlessCheck`); no unrelated changes. - For behavior changes, include before/after notes or relevant logs. ## Tips & Environment - Useful env vars: `JAVA_HOME`, `TEST_JAVA_HOME`, `BTRACE_TEST_DEBUG=true` (verbose integration tests), optional `BTRACE_HOME` when using the exploded dist. - Example exploded dist path: `btrace-dist/build/resources/main/v2.2.6/`. ### Restricted/CI Environments - Prefer a workspace-local Gradle cache to avoid permission issues: set `GRADLE_USER_HOME=$(pwd)/.gradle-user`. - If network interfaces are restricted, force IPv4 to avoid wildcard IP detection errors: set `JAVA_TOOL_OPTIONS="-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false"`. - Example: `GRADLE_USER_HOME=$(pwd)/.gradle-user JAVA_TOOL_OPTIONS="-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false" ./gradlew :btrace-dist:buildZip -x test` ## Common Patterns & Lessons Learned ### Adding New Classes 1. **Determine the section**: Is the class used by agent, client, or both? - Agent only → prepareAgentClassdata include pattern - Client only → prepareClientClassdata include pattern - Both → prepareSharedClassdata include pattern 2. **Update build.gradle**: Add include pattern in the appropriate task 3. **Rebuild and test**: `./gradlew clean :btrace-dist:btraceJar && ./gradlew -Pintegration test` ### Dependency Relocation - All third-party dependencies are relocated to `org.openjdk.btrace.libs.*` - Relocations happen in `allClassesShadow` task using Shadow plugin - Common relocations: ASM, SLF4J, JCTools - After relocation, classes are extracted and masked in prepare*Classdata tasks ### Build Simplification Wins - **Before**: Separate agent.jar, client.jar, boot.jar, uber.jar (4 artifacts) - **After**: Single btrace.jar with masked sections (1 artifact) - **Result**: Simpler build, smaller distribution, easier maintenance ### ClassLoader Isolation - Bootstrap classes can see everything (including masked sections via MaskedClassLoader) - Application classes cannot see masked sections (isolation prevents conflicts) - Masked classes in agent mode cannot see masked classes in client mode (intentional isolation) - Shared section solves cross-mode visibility when needed (e.g., command serialization) ## Hard rules - Never commit changes unless they are fully tested or you are explicitly asked to commit - Do not use FQNs directly! Always import types and use simple type names in the code! - When adding classes to masked jar, always consider: agent-only, client-only, or shared? - Rebuild the distribution after any changes to masked jar structure: `./gradlew clean :btrace-dist:btraceJar` ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to BTrace Thanks for your interest in contributing! This guide covers local development, running tests, Gradle tips, and common troubleshooting. Note: Pull requests can only be accepted from signers of the Oracle Contributor Agreement (OCA). See the project README for details. ## Local Development - JDK: Use a reasonably recent JDK (11+ recommended). The project targets a broad range but tests run comfortably on 11/17. - Wrapper: Use the bundled `./gradlew` wrapper. It will download the pinned Gradle version if needed. - Local Gradle cache (optional but recommended): - macOS/Linux: `export GRADLE_USER_HOME="$PWD/.gradle-home"` - Windows (PowerShell): `$env:GRADLE_USER_HOME = "$PWD/.gradle-home"` ## Running Tests - All unit tests (skip integration tests): ```sh ./gradlew --no-daemon test -x integration-tests:test ``` - Per-module tests: - Runtime: `./gradlew :btrace-runtime:test` - Extension: `./gradlew :btrace-extension:test` - Compiler: `./gradlew :btrace-compiler:test` - Instr: `./gradlew :btrace-instr:test` - Update instrumentor goldens when bytecode output changes: ```sh ./gradlew test -PupdateTestData ``` - Integration tests (spawn JVMs, exercise agent and extensions): ```sh ./gradlew --no-daemon integration-tests:test ``` - If tests fail due to denied privileged extensions, pass a policy file to the tested JVMs: - Create `permissions.properties`: ```properties allowPrivileged=true allowExtensions=btrace-metrics,btrace-utils ``` - Export path: `export BTRACE_PERMS=$PWD/permissions.properties` - Run Gradle with: `-Dbtrace.permissions=$BTRACE_PERMS` ## Gradle Tips - Prefer IPv4 if your environment has unusual local IP settings (helps Gradle select a wildcard address): ```sh export GRADLE_OPTS="-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false" ``` - Enable Gradle debug output for flakiness: add `--info` or `--debug`. - Run a single test class/method: ```sh ./gradlew :btrace-extension:test --tests org.openjdk.btrace.extension.ExtensionBridgeImplPolicyTest ./gradlew :btrace-runtime:test --tests "*ExtensionIndyShimIndexTest.resolvesNoopShimFromIndex" ``` ## Troubleshooting - Gradle wrapper needs to download Gradle: ensure network is allowed once; subsequent runs use the local cache under `.gradle-home`. - Error: `Could not determine a usable wildcard IP for this machine`: - Set the IPv4 flags shown above or ensure local networking is available. - Permission errors when Gradle writes outside the workspace: - Use a local Gradle cache via `GRADLE_USER_HOME` as shown above. - Integration tests failing with permissions denied: - Provide a policy file and pass it via `-Dbtrace.permissions=/path/to/permissions.properties`. ## Code Style & Scope - Keep changes focused and minimal; align with existing code style. - Update docs when changing user-visible behavior. - Prefer clear separation of concerns and small helpers over inlined, complex logic. - Avoid introducing new dependencies without discussion. ## Submitting a PR 1. Fork the repo and branch from `develop` (unless otherwise agreed). 2. Make your changes and run tests locally. 3. If instrumentor behavior changed, update goldens (`-PupdateTestData`) and include them in your commit. 4. Submit a PR with a concise description of the change, rationale, and any follow-ups. Happy tracing! ================================================ FILE: LICENSE ================================================ The GNU General Public License (GPL) Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. One line to give the program's name and a brief idea of what it does. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free software, and you are welcome to redistribute it under certain conditions; type 'show c' for details. The hypothetical commands 'show w' and 'show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than 'show w' and 'show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program 'Gnomovision' (which makes passes at compilers) written by James Hacker. signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. "CLASSPATH" EXCEPTION TO THE GPL Certain source files distributed by Oracle America and/or its affiliates are subject to the following clarification and special exception to the GPL, but only where Oracle has expressly included in the particular source file's header the words "Oracle designates this particular file as subject to the "Classpath" exception as provided by Oracle in the LICENSE file that accompanied this code." Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. [All 3rd party libraries licenses are listed in LICENSE-3RD-PARTY.txt file] ================================================ FILE: README.md ================================================ # BTrace **Safe, dynamic tracing for Java applications** [![CI](https://github.com/btraceio/btrace/workflows/BTrace%20CI%2FCD/badge.svg?branch=develop)](https://github.com/btraceio/btrace/actions) [![Release](https://img.shields.io/github/v/release/btraceio/btrace?sort=semver)](https://github.com/btraceio/btrace/releases/latest) [![codecov](https://codecov.io/github/btraceio/btrace/coverage.svg?branch=develop)](https://codecov.io/github/btraceio/btrace?branch=develop) BTrace dynamically instruments running Java applications to inject tracing code at runtime. No restarts. No recompilation. Production-safe. > **Quick links:** [Quick Reference](docs/QuickReference.md) · [Step-by-Step Tutorial](docs/GettingStarted.md) --- ## Why BTrace? - **Zero downtime** - Attach to running JVMs without restart - **Production safe** - Verified scripts can't crash your application - **Flexible probes** - Method entry/exit, timings, field access, allocations - **Low overhead** - Bytecode injection with minimal performance impact --- ## Get Started in 30 Seconds ```sh # Install via JBang (easiest) curl -Ls https://sh.jbang.dev | bash -s - app setup # Add the BTrace JBang catalog (one time) jbang catalog add --name btraceio https://raw.githubusercontent.com/btraceio/jbang-catalog/main/jbang-catalog.json # Trace slow methods in your running app jbang btrace@btraceio -n 'com.myapp.*::* @return if duration>100ms { print method, duration }' $(pgrep -f myapp) ``` --- ## Trace Anything **Method timing:** ```sh btrace -n 'java.sql.Statement::execute* @return { print method, duration }' ``` **Exception tracking:** ```sh btrace -n 'java.lang.Exception:: @return { print self, stack(5) }' ``` **Custom probes:** ```java @BTrace public class Trace { @OnMethod(clazz = "com.example.OrderService", method = "checkout") public static void onCheckout(@Self Object self, @Duration long ns) { println(strcat("checkout: ", str(ns/1_000_000) + "ms")); } } ``` See the [Oneliner Guide](docs/OnelinerGuide.md) for complete syntax. --- ## Install ```sh # JBang (recommended - zero installation) jbang catalog add --name btraceio https://raw.githubusercontent.com/btraceio/jbang-catalog/main/jbang-catalog.json jbang btrace@btraceio script.java # SDKMan sdk install btrace # Manual download curl -LO https://github.com/btraceio/btrace/releases/latest/download/btrace-bin.tar.gz ``` See [Installation Guide](docs/GettingStarted.md#installation) for Docker, package managers, and more options. --- ## Documentation | Resource | Description | |----------|-------------| | [Quick Reference](docs/QuickReference.md) | Cheat sheet for experienced users | | [Getting Started](docs/GettingStarted.md) | Step-by-step first trace tutorial | | [Full Tutorial](docs/BTraceTutorial.md) | Complete walkthrough of all features | | [Oneliners](docs/OnelinerGuide.md) | DTrace-style quick probes | | [Extensions](docs/BTraceExtensionDevelopmentGuide.md) | StatsD, custom integrations | | [Documentation Hub](docs/README.md) | All docs and guides | --- ## Building from Source ```sh git clone https://github.com/btraceio/btrace.git cd btrace ./gradlew :btrace-dist:build ``` See [CLAUDE.md](CLAUDE.md) for development setup and architecture. --- ## Community & Contributing **Get help:** [Slack](http://btrace.slack.com/) · [Gitter](https://gitter.im/btraceio/btrace) · [GitHub Issues](https://github.com/btraceio/btrace/issues) **Contribute:** Pull requests require signing the [Oracle Contributor Agreement](https://oca.opensource.oracle.com/). --- ## License GPLv2 with Classpath Exception. See [LICENSE](LICENSE). --- **Credits:** Built with [ASM](http://asm.ow2.org/), [JCTools](https://github.com/JCTools/JCTools), [hppcrt](https://github.com/vsonnier/hppcrt). Optimized with [JProfiler](http://www.ej-technologies.com/products/jprofiler/overview.html). ================================================ FILE: benchmarks/agent-benchmark/build.gradle ================================================ plugins { id 'java' alias(libs.plugins.jmh) } description 'A JMH benchmark to assert the overhead imposed by various types of BTrace instrumentation.' def env = System.getenv() def javaHome = env['JAVA_HOME'] dependencies { implementation project(path: ":btrace-dist", configuration: "shadow") implementation project(":btrace-compiler") jmh tasks.getByPath(':btrace-dist:btraceJar').outputs.getFiles() jmh libs.jmh jmh libs.jmh.annprocess } task btracec(type: JavaExec) { group 'Build' inputs.files 'src/main/resources/scripts' outputs.dir buildDir.toPath().resolve("classes/java/main") environment('BTRACE_HOME', "$projectDir") classpath configurations.runtimeClasspath mainClass = 'org.openjdk.btrace.compiler.Compiler' args '-d' args "${buildDir}/classes/java/main/" args '-packext' args 'btclass' args fileTree(dir: "src/jmh/btrace", include: 'TraceScript.java') } compileJmhJava.dependsOn btracec jmhClasses.dependsOn btracec jmhJar { include 'META-INF/BenchmarkList' include 'META-INF/CompilerHints' include 'org/openjdk/jmh/**' include 'org/openjdk/btrace/bench/**/*.class' include 'org/openjdk/btrace/generated/**/*' include "joptsimple/**" include "org/apache/**" include 'jmh*' include 'benchmark/**' include '*.btclass' } jmh { warmupIterations = 5 iterations = 10 fork = 2 jvm = "${env['JAVA_HOME']}/bin/java" duplicateClassesStrategy = DuplicatesStrategy.WARN def agentJarPath = tasks.getByPath(':btrace-dist:btraceJar').outputs.getFiles().getSingleFile() def scriptPath = buildDir.toPath().resolve('classes/java/main/TraceScript.btclass') def agent = "-javaagent:${agentJarPath}=stdout=false,noServer=true,debug=false,script=${scriptPath}" jvmArgsAppend = ["-Djmh.basedir=${buildDir.getParentFile()}", "-Dproject.version=${project.version}", "-Xmx128m", "-agentpath:/tmp/libasyncProfiler.dylib=start,event=cpu,jfr=7,file=/tmp/btrace.jfr", "${agent}"] includes = ['.*BTraceBench.*'] profilers = ['stack'] } ================================================ FILE: benchmarks/agent-benchmark/src/jmh/btrace/TraceScript.java ================================================ import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Duration; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Level; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Sampled; @BTrace public class TraceScript { @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethod") public static void onMethodEntryEmpty(@ProbeClassName String pcn, @ProbeMethodName String pmn) {} @OnMethod( clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodLevelNoMatch", enableAt = @Level("100")) public static void onMethodEntryEmptyLevelNoMatch( @ProbeClassName String pcn, @ProbeMethodName String pmn) {} @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodSampled") @Sampled(kind = Sampled.Sampler.Const) public static void onMethodEntryEmptySampled( @ProbeClassName String pcn, @ProbeMethodName String pmn) {} @OnMethod( clazz = "benchmark.BTraceBench", method = "testInstrDuration", location = @Location(Kind.RETURN)) public static void onMethodRetDuration( @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} @OnMethod( clazz = "benchmark.BTraceBench", method = "testInstrDurationSampled", location = @Location(Kind.RETURN)) @Sampled(kind = Sampled.Sampler.Const) public static void onMethodRetDurationSampled( @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} @OnMethod( clazz = "benchmark.BTraceBench", method = "testInstrDurationSampledAdaptive", location = @Location(Kind.RETURN)) @Sampled public static void onMethodRetDurationSampledAdaptive( @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln1") public static void onMethodEntryPrintln1( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); } @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln1Sampled") @Sampled public static void onMethodEntryPrintln1Sampled( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); } @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln2") public static void onMethodEntryPrintln2( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); println(pmn); } @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln3") public static void onMethodEntryPrintln3( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); println(pmn); println(pmn); } @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln24") public static void onMethodEntryPrintln24( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); } } ================================================ FILE: benchmarks/agent-benchmark/src/jmh/java/benchmark/BTraceBench.java ================================================ /* * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package benchmark; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.Random; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.core.comm.CommandListener; import org.openjdk.btrace.instr.MethodTracker; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class BTraceBench { private static class BTraceConfig { private final String agentJar; private final String scriptPath; private final Path tmpRoot; private static final FileVisitor DEL_TREE = new FileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.TERMINATE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }; public BTraceConfig(Path tmpRoot, String agentJar, String scriptPath) { this.agentJar = agentJar; this.scriptPath = scriptPath; this.tmpRoot = tmpRoot; } public void cleanup() throws IOException { Files.walkFileTree(tmpRoot, DEL_TREE); } } long counter; long sampleCounter; long durCounter; @Setup public void setup() { MethodTracker.registerCounter(1, 10); MethodTracker.registerCounter(2, 50); MethodTracker.registerCounter(3, 100); Random r = new Random(System.currentTimeMillis()); sampleCounter = 0; durCounter = 0; counter = r.nextInt(); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethod() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodLevelNoMatch() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodSampled() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodPrintln1() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodPrintln1Sampled() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodPrintln2() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodPrintln3() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrumentedMethodPrintln24() { counter++; } @Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testMethod() { counter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrDuration() { durCounter++; } public boolean x = true; @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrDurationSampled() { sampleCounter++; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testInstrDurationSampledAdaptive() { sampleCounter++; } // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) // @Benchmark // public void testSendCommand() { // br.send(new OkayCommand()); // } // // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) // @Threads(2) // @Benchmark // public void testSendCommandMulti2() { // br.send(new OkayCommand()); // } // // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) // @Threads(4) // @Benchmark // public void testSendCommandMulti4() { // br.send(new OkayCommand()); // } // // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) // @Threads(8) // @Benchmark // public void testSendCommandMulti8() { // br.send(new OkayCommand()); // } // // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) // @Threads(16) // @Benchmark // public void testSendCommandMulti16() { // br.send(new OkayCommand()); // } long sampleHit10Checks = 0; long sampleHit10Sampled = 0; @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(2) public void testSampleHit10() { sampleHit10Checks++; if (MethodTracker.hit(1)) { sampleHit10Sampled++; } } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(2) public void testSampleHit50() { sampleHit10Checks++; if (MethodTracker.hit(2)) { sampleHit10Sampled++; } } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(2) public void testSampleHit100() { sampleHit10Checks++; if (MethodTracker.hit(3)) { sampleHit10Sampled++; } } @org.openjdk.jmh.annotations.TearDown public void teardown() { if (sampleHit10Checks > 0) { System.err.println("=== testSampleHit10"); System.err.println("#samples ~ " + sampleHit10Sampled); if (sampleHit10Sampled > 0) { System.err.println("#sampling rate ~ " + (sampleHit10Checks / sampleHit10Sampled)); } } } public static void main(String[] args) throws Exception { BTraceConfig bc = getConfig(); try { Options opt = new OptionsBuilder() .addProfiler("stack") // .jvmArgsPrepend( // "-javaagent:" // + bc.agentJar // + "=stdout=false,noServer=true," // + "script=" // + bc.scriptPath) .include(".*" + BTraceBench.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } finally { bc.cleanup(); } } private static BTraceConfig getConfig() throws IOException { FileSystem fs = FileSystems.getDefault(); Path distLibs = null; String basedir = System.getProperty("jmh.basedir"); String version = System.getProperty("project.version"); String scriptPath = System.getProperty("script.path"); Path root = null; if (basedir == null) { root = fs.getPath(".").toAbsolutePath(); } else { root = Paths.get(basedir).getParent(); } distLibs = root.resolve("btrace-dist/build/resources/main/" + version + "/libs"); Path agentPath = distLibs.resolve("btrace-agent.jar"); Path bootPath = distLibs.resolve("btrace-boot.jar"); Path tmpDir = Files.createTempDirectory("btrace-bench-"); Path targetPath = Files.copy( agentPath, tmpDir.resolve("btrace-agent.jar"), StandardCopyOption.REPLACE_EXISTING); Files.copy(bootPath, tmpDir.resolve("btrace-boot.jar"), StandardCopyOption.REPLACE_EXISTING); return new BTraceConfig(tmpDir, targetPath.toString(), scriptPath + "/TraceScript.btclass"); } } ================================================ FILE: benchmarks/runtime-benchmarks/build.gradle ================================================ plugins { id 'java' alias(libs.plugins.jmh) } description 'A JMH benchmark to assert the overhead imposed by various types of BTrace instrumentation.' def env = System.getenv() def javaHome = env['JAVA_HOME'] configurations { compilerDeps } dependencies { implementation project(path: ":btrace-dist", configuration: "shadow") implementation project(":btrace-compiler") jmh project(":btrace-instr") jmh project(":btrace-runtime") jmh project(":btrace-extensions:btrace-statsd") jmh libs.jmh jmh libs.jmh.annprocess compilerDeps project(path: ":btrace-dist", configuration: "shadow") compilerDeps project(":btrace-compiler") } task btracec(type: JavaExec) { group 'Build' inputs.files 'src/main/resources/scripts' outputs.dir "${buildDir}/classes/java/main" environment('BTRACE_HOME', "$projectDir") classpath configurations.compilerDeps mainClass = 'org.openjdk.btrace.compiler.Compiler' args '-d' args "${buildDir}/classes/java/main/" args '-packext' args 'btclass' args fileTree(dir: "src/jmh/btrace", include: 'TraceScript.java') } compileJmhJava.dependsOn btracec jmhClasses.dependsOn btracec jmhJar { include 'META-INF/BenchmarkList' include 'META-INF/CompilerHints' include 'org/jctools/**/*' include 'org/objectweb/asm/**' include 'org/openjdk/jmh/**' include 'org/openjdk/btrace/bench/**/*.class' include "org/openjdk/btrace/core/**" include "org/openjdk/btrace/instr/**" include 'org/openjdk/btrace/generated/**/*' include 'org/openjdk/btrace/runtime/**' // no legacy services classes to package include "joptsimple/**" include "org/apache/**" include '*.btclass' include 'jmh*' } jmh { duplicateClassesStrategy = DuplicatesStrategy.WARN jvmArgsAppend = ["-Djmh.basedir=${project.buildDir.getParent()}", "-Dproject.version=${project.version}"] // jmhVersion = '1.27' includes = ['org.openjdk.btrace.bench.ClassFilterBenchmark'] verbosity = 'EXTRA' } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/btrace/TraceScript.java ================================================ import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Duration; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Level; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Sampled; @BTrace public class TraceScript { @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethod") public static void onMethodEntryEmpty(@ProbeClassName String pcn, @ProbeMethodName String pmn) {} @OnMethod( clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodLevelNoMatch", enableAt = @Level("100")) public static void onMethodEntryEmptyLevelNoMatch( @ProbeClassName String pcn, @ProbeMethodName String pmn) {} @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodSampled") @Sampled(kind = Sampled.Sampler.Const) public static void onMethodEntryEmptySampled( @ProbeClassName String pcn, @ProbeMethodName String pmn) {} @OnMethod( clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrDuration", location = @Location(Kind.RETURN)) public static void onMethodRetDuration( @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} @OnMethod( clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrDurationSampled", location = @Location(Kind.RETURN)) @Sampled(kind = Sampled.Sampler.Const) public static void onMethodRetDurationSampled( @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} @OnMethod( clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrDurationSampledAdaptive", location = @Location(Kind.RETURN)) @Sampled public static void onMethodRetDurationSampledAdaptive( @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln1") public static void onMethodEntryPrintln1( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); } @OnMethod( clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln1Sampled") @Sampled public static void onMethodEntryPrintln1Sampled( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); } @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln2") public static void onMethodEntryPrintln2( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); println(pmn); } @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln3") public static void onMethodEntryPrintln3( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); println(pmn); println(pmn); } @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln24") public static void onMethodEntryPrintln24( @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); println(pcn); println(pmn); println(pmn); } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ClassFilterBenchmark.java ================================================ /* * Copyright (c) 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.bench; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.instr.ClassFilter; import org.openjdk.btrace.instr.OnMethod; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class ClassFilterBenchmark { private static final String CLASS_A_PKG = "org.openjdk.btrace.benchmark"; private static final String CLASS_A_NAME = "ClassA"; private static final String CLASS_A = CLASS_A_PKG + "." + CLASS_A_NAME; private ClassFilter cfSimple; private ClassFilter cfRegexName; private ClassFilter cfSubtype; @Setup public void setup() { OnMethod simpleClassFilter = new OnMethod(); simpleClassFilter.setClazz(CLASS_A); OnMethod regexNameFilter = new OnMethod(); regexNameFilter.setClazz("/.*\\." + CLASS_A_NAME + "/"); OnMethod subtypeFilter = new OnMethod(); subtypeFilter.setClazz("+java.util.List"); cfSimple = new ClassFilter(Collections.singleton(simpleClassFilter)); cfRegexName = new ClassFilter(Collections.singleton(regexNameFilter)); cfSubtype = new ClassFilter(Collections.singleton(subtypeFilter)); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testSimpleClassNameMatch(Blackhole bh) { bh.consume(cfSimple.isNameMatching(CLASS_A)); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testRegexNameMatch(Blackhole bh) { bh.consume(cfRegexName.isNameMatching(CLASS_A)); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testSubtypeMatch(Blackhole bh) { bh.consume(cfSubtype.isCandidate(ArrayList.class)); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testSubtypeNoMatch(Blackhole bh) { bh.consume(cfSubtype.isCandidate(String.class)); } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .addProfiler("stack") .include(".*" + ClassFilterBenchmark.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/DispatchBenchmark.java ================================================ /* * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.bench; import java.lang.invoke.CallSite; import java.lang.invoke.ConstantCallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.MutableCallSite; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; /** * JMH benchmark measuring INVOKEDYNAMIC dispatch overhead as simulated by * {@link ConstantCallSite} — the mechanism used by {@code IndyDispatcher}. * *

Compares a plain static method call ({@link #baseline}) against dispatch * through a {@link ConstantCallSite} ({@link #instrumented}). */ @State(Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class DispatchBenchmark { private MethodHandle constantTarget; private MethodHandle mutableTarget; @Setup(Level.Trial) public void setup() throws Exception { // Build a ConstantCallSite targeting the static handler method, simulating what // IndyDispatcher.bootstrap() produces. MethodHandle mh = MethodHandles.lookup() .findStatic( DispatchBenchmark.class, "probeHandler", MethodType.methodType(void.class, int.class)); CallSite cs = new ConstantCallSite(mh); constantTarget = cs.dynamicInvoker(); MutableCallSite mcs = new MutableCallSite(mh.type()); mcs.setTarget(mh); mutableTarget = mcs.dynamicInvoker(); } /** Direct static call — baseline with zero dispatch overhead. */ @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) @Benchmark public void baseline(Blackhole bh) { probeHandler(42); } /** Dispatch through a ConstantCallSite — simulates IndyDispatcher-resolved call site. */ @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) @Benchmark public void instrumented(Blackhole bh) throws Throwable { constantTarget.invokeExact(42); } /** * Dispatch through a MutableCallSite whose target is stable (set once, never re-linked). * Simulates the IndyDispatcher-post-detach-safety variant. HotSpot should treat the target * as @Stable and inline through it comparably to ConstantCallSite. */ @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) @Benchmark public void instrumentedMutable(Blackhole bh) throws Throwable { mutableTarget.invokeExact(42); } /** Simulated probe handler method. */ public static void probeHandler(int value) { // intentionally empty — we measure dispatch cost, not handler body cost } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/OnMethodTemplateBenchmark.java ================================================ /* * Copyright (c) 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.bench; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class OnMethodTemplateBenchmark { private ArgsMap argsMap; @Setup public void setup() { argsMap = new ArgsMap(new String[] {"arg1=val1"}); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testEmptyTemplate(Blackhole bh) { bh.consume(argsMap.template("")); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testMatchTemplate(Blackhole bh) { bh.consume(argsMap.template("this-is-${arg1}")); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testNoMatchTemplate(Blackhole bh) { bh.consume(argsMap.template("this-is-${arg2}")); } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .addProfiler("stack") .include(".*" + OnMethodTemplateBenchmark.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProbeLoadingBenchmark.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.bench; import java.io.*; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.instr.BTraceProbe; import org.openjdk.btrace.instr.BTraceProbeFactory; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class ProbeLoadingBenchmark { private InputStream classStream; private BTraceProbeFactory bpf; @Setup(Level.Trial) public void setup() throws Exception { bpf = new BTraceProbeFactory(SharedSettings.GLOBAL); } @Setup(Level.Invocation) public void setupRun() throws Exception { classStream = ProbeLoadingBenchmark.class.getResourceAsStream("/TraceScript.btclass"); } @TearDown(Level.Invocation) public void tearDownRun() throws Exception { classStream.close(); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) @Benchmark public void testBTraceProbeNew(Blackhole bh) throws Exception { BTraceProbe bp = bpf.createProbe(classStream); if (bp == null) { throw new NullPointerException(); } bh.consume(bp); } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .addProfiler("stack") .include(".*" + ProbeLoadingBenchmark.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProfilerBenchmark.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.bench; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.runtime.profiling.MethodInvocationProfiler; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.VerboseMode; /** * Basic benchmark for the performance of {@linkplain MethodInvocationProfiler} * * @author Jaroslav Bachorik */ @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class ProfilerBenchmark { private MethodInvocationProfiler mip1; private MethodInvocationProfiler mip2; @Setup public void setup() { mip1 = new MethodInvocationProfiler(1); mip2 = new MethodInvocationProfiler(500); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(1) public void testOneMethodSingleThread() { mip1.recordEntry("a"); mip1.recordExit("a", 1); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(1) public void testTwoMethods01Thread() { mip2.recordEntry("a"); mip2.recordEntry("b"); mip2.recordExit("b", 10); mip2.recordExit("a", 1); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(2) public void testTwoMethods02Threads() { mip2.recordEntry("a"); mip2.recordEntry("b"); mip2.recordExit("b", 10); mip2.recordExit("a", 1); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(4) public void testTwoMethods04Threads() { mip2.recordEntry("a"); mip2.recordEntry("b"); mip2.recordExit("b", 10); mip2.recordExit("a", 1); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(8) public void testTwoMethods08Threads() { mip2.recordEntry("a"); mip2.recordEntry("b"); mip2.recordExit("b", 10); mip2.recordExit("a", 1); } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(16) public void testTwoMethods16Threads() { mip2.recordEntry("a"); mip2.recordEntry("b"); mip2.recordExit("b", 10); mip2.recordExit("a", 1); } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .addProfiler("stack") .verbosity(VerboseMode.NORMAL) .include(".*" + ProfilerBenchmark.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StatsdBenchmark.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.bench; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.statsd.Statsd; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; /** * Basic benchmark for the performance of {@linkplain Statsd} * * @author Jaroslav Bachorik */ @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class StatsdBenchmark { private Statsd c; @Setup public void setup() { // Inline no-op impl — the benchmark measures dispatch through the extension API, // not the network-layer Statsd implementation. c = new Statsd() { @Override public void increment(String name) {} @Override public void increment(String name, String tags) {} }; } @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Benchmark @Threads(1) public void testIncrement_1() { c.increment("g1"); } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .addProfiler("stack") .include(".*" + StatsdBenchmark.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StringOpBenchmark.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.bench; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Fork(1) @BenchmarkMode(Mode.AverageTime) public class StringOpBenchmark { private static final String STRING_PART = "h"; StringBuilder sb; String st; String res; @Setup public void setup() { st = ""; } @Setup(Level.Invocation) public void setupEach() { sb = new StringBuilder(); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testStringBuilder() { sb.append(STRING_PART).append(STRING_PART); } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testStringPlus() { res = st + STRING_PART + STRING_PART; } @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) @Benchmark public void testStrCat() { res = st.concat(STRING_PART).concat(STRING_PART); } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .addProfiler("gc") .include(".*" + StringOpBenchmark.class.getSimpleName() + ".*test.*") .build(); new Runner(opt).run(); } } ================================================ FILE: btrace-agent/build.gradle ================================================ compileJava { // Keep Java 8 compatibility while accessing JDK internal APIs options.fork = true options.forkOptions.jvmArgs += [ '--add-exports', 'jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED' ] } javadoc { // Javadoc also needs access to internal APIs - but Java 8 javadoc doesn't support --add-exports // So we exclude the file that uses internal APIs from javadoc generation exclude '**/PerfReaderImpl.java' } dependencies { implementation libs.slf4j implementation libs.asm def toolsJar = getToolsJar(); if (toolsJar.getAsFile().exists()) { runtimeOnly files("${toolsJar}") } implementation project(':btrace-core') implementation project(':btrace-runtime') implementation project(':btrace-instr') implementation project(':btrace-extension') } // Exclude sources that rely on JDK-internal modules from Javadoc to avoid missing-package errors tasks.named('javadoc').configure { exclude 'org/openjdk/btrace/agent/PerfReaderImpl.java' } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceRuntimeBridge; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.CommandListener; import org.openjdk.btrace.core.comm.ErrorCommand; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.openjdk.btrace.core.comm.RenameCommand; import org.openjdk.btrace.core.comm.RetransformationStartNotification; import org.openjdk.btrace.core.comm.StatusCommand; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.PermissionSet; import org.openjdk.btrace.extension.ExtensionDescriptorDTO; import org.openjdk.btrace.extension.ExtensionLoader; import org.openjdk.btrace.extension.ExtensionRegistry; import org.openjdk.btrace.instr.BTraceProbe; import org.openjdk.btrace.instr.BTraceProbeFactory; import org.openjdk.btrace.instr.BTraceProbePersisted; import org.openjdk.btrace.instr.BTraceTransformer; import org.openjdk.btrace.instr.ClassCache; import org.openjdk.btrace.instr.ClassFilter; import org.openjdk.btrace.instr.ClassInfo; import org.openjdk.btrace.instr.InstrumentUtils; import org.openjdk.btrace.instr.Instrumentor; import org.openjdk.btrace.instr.MethodTrackingContext; import org.openjdk.btrace.runtime.BTraceRuntimeAccess; import org.openjdk.btrace.runtime.BTraceRuntimes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.lang.management.ManagementFactory; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Abstract class that represents a BTrace client at the BTrace agent. * * @author A. Sundararajan * @author J. Bachorik (j.bachorik@btrace.io) */ abstract class Client implements CommandListener { private static final Logger log = LoggerFactory.getLogger(Client.class); private static final Map CLIENTS = new ConcurrentHashMap<>(); private static final Map WRITER_MAP = new HashMap<>(); private static final Pattern SYSPROP_PTN = Pattern.compile("\\$\\{(.+?)}"); static { ClassFilter.class.getClassLoader(); InstrumentUtils.class.getClassLoader(); Instrumentor.class.getClassLoader(); ClassReader.class.getClassLoader(); ClassWriter.class.getClassLoader(); Annotation.class.getClassLoader(); MethodTrackingContext.class.getClassLoader(); ClassCache.class.getClassLoader(); ClassInfo.class.getClassLoader(); } private final Instrumentation inst; final SharedSettings settings; final ArgsMap argsMap; private final BTraceTransformer transformer; volatile PrintWriter out; private volatile BTraceRuntime.Impl runtime; private volatile String outputName; private BTraceProbe probe; private Timer flusher; private volatile boolean initialized = false; private volatile boolean shuttingDown = false; final UUID id = UUID.randomUUID(); Client(ClientContext ctx) { this(ctx.getInstr(), ctx.getArguments(), ctx.getSettings(), ctx.getTransformer()); } private Client(Instrumentation inst, ArgsMap argsMap, SharedSettings s, BTraceTransformer t) { this.inst = inst; this.argsMap = argsMap; settings = s != null ? s : SharedSettings.GLOBAL; transformer = t; setupWriter(); CLIENTS.put(id, this); } private static String pid() { String pName = ManagementFactory.getRuntimeMXBean().getName(); if (pName != null && pName.length() > 0) { String[] parts = pName.split("@"); if (parts.length == 2) { return parts[0]; } } return "-1"; } protected final void initialize() { initialized = true; } @SuppressWarnings("DefaultCharset") private final void setupWriter() { String outputFile = settings.getOutputFile(); if (outputFile == null || outputFile.equals("::null") || outputFile.equals("/dev/null")) return; if (!outputFile.equals("::stdout")) { String outputDir = settings.getScriptDir(); String output = (outputDir != null ? outputDir + File.separator : "") + outputFile; outputFile = templateOutputFileName(output); log.info("Redirecting output to {}", outputFile); } out = WRITER_MAP.get(outputFile); if (out == null) { if (outputFile.equals("::stdout")) { out = new PrintWriter(System.out); } else { if (settings.getFileRollMilliseconds() > 0) { out = new PrintWriter( new BufferedWriter( TraceOutputWriter.rollingFileWriter(new File(outputFile), settings))); } else { out = new PrintWriter( new BufferedWriter(TraceOutputWriter.fileWriter(new File(outputFile)))); } } WRITER_MAP.put(outputFile, out); out.append("### BTrace Log: ") .append(DateFormat.getInstance().format(new Date())) .append("\n\n"); startFlusher(); } outputName = outputFile; } private void startFlusher() { int flushInterval; String flushIntervalStr = System.getProperty("org.openjdk.btrace.FileClient.flush"); if (flushIntervalStr == null) { flushIntervalStr = System.getProperty("com.sun.btrace.FileClient.flush", "5"); } try { flushInterval = Integer.parseInt(flushIntervalStr); } catch (NumberFormatException e) { flushInterval = 5; // default } int flushSec = flushInterval; if (flushSec > -1) { flusher = new Timer("BTrace FileClient Flusher", true); flusher.scheduleAtFixedRate( new TimerTask() { @Override public void run() { try { if (out != null) { boolean entered = BTraceRuntime.enter(); try { out.flush(); } finally { if (entered) { BTraceRuntime.leave(); } } } } catch (Throwable t) { log.error("Error during periodic flush", t); } } }, flushSec, flushSec); } else { flusher = null; } } private String templateOutputFileName(String fName) { if (fName != null) { boolean dflt = fName.contains("[default]"); String agentName = System.getProperty("btrace.agent", "default"); String clientName = settings.getClientName(); fName = fName .replace("${client}", clientName != null ? clientName : "") .replace("${ts}", String.valueOf(System.currentTimeMillis())) .replace("${pid}", pid()) .replace("${agent}", agentName != null ? "." + agentName : "") .replace("[default]", ""); fName = replaceSysProps(fName); if (dflt && log.isDebugEnabled()) { log.debug("scriptOutputFile not specified. defaulting to {}", fName); } } return fName; } private String replaceSysProps(String str) { int replaced = 0; do { StringBuffer sb = new StringBuffer(); replaced = replaceSysProps(str, sb); str = sb.toString(); } while (replaced > 0); return str; } private int replaceSysProps(String str, StringBuffer sb) { int cnt = 0; Matcher m = SYSPROP_PTN.matcher(str); while (m.find()) { String key = m.group(1); String val = System.getProperty(key); if (val != null) { cnt++; m.appendReplacement(sb, val); } else { m.appendReplacement(sb, m.group(0)); } } m.appendTail(sb); return cnt; } static Collection listProbes() { List probes = new ArrayList<>(CLIENTS.size()); for (Client client : CLIENTS.values()) { if (client instanceof RemoteClient) { if (((RemoteClient) client).isDisconnected()) { probes.add(client.id + " [" + client.getClassName() + "]"); } } } return probes; } synchronized void onExit(int exitCode) { if (!shuttingDown) { shuttingDown = true; if (out != null) { out.flush(); } BTraceRuntime.leave(); try { log.debug("onExit:"); log.debug("cleaning up transformers"); cleanupTransformers(); log.debug("removing instrumentation"); retransformLoaded(); log.debug("closing all I/O"); // Send EXIT command to notify remote client before closing sendCommand(new ExitCommand(exitCode)); Thread.sleep(300); try { closeAll(); } catch (IOException e) { // ignore IOException when closing } log.debug("done"); } catch (Throwable th) { // ExitException is expected here if (!th.getClass().getName().equals("ExitException")) { log.debug("Failed to gracefully exit BTrace probe", th); BTraceRuntime.handleException(th); } } finally { runtime.shutdownCmdLine(); CLIENTS.remove(id); } } } final synchronized Class loadClass(InstrumentCommand instr) throws IOException { ArgsMap args = instr.getArguments(); byte[] btraceCode = instr.getCode(); try { probe = load(btraceCode, ArgsMap.merge(argsMap, args)); if (probe == null) { log.debug("Failed to load BTrace probe code"); return null; } if (!settings.isTrusted()) { probe.checkVerified(); } // Check probe's required permissions against effective permissions Set required = probe.getRequiredPermissions(); if (!required.isEmpty()) { PermissionSet effective = settings.getEffectivePermissions(); Set missing = required.stream().filter(p -> !effective.has(p)).collect(Collectors.toSet()); if (!missing.isEmpty()) { throw new SecurityException(formatPermissionError(missing)); } } // Validate that all injected service types are declared by available extensions validateDeclaredServices(probe); } catch (Throwable th) { log.debug("Failed to load BTrace probe code", th); errorExit(th); return null; } if (log.isDebugEnabled()) { log.debug("creating BTraceRuntime instance for {}", probe.getClassName()); } runtime = BTraceRuntimes.getRuntime(probe.getClassName(), args, this, inst); Runtime.getRuntime() .addShutdownHook( new Thread( () -> { if (runtime != null) { runtime.handleExit(0); } })); if (probe.isClassRenamed()) { if (log.isDebugEnabled()) { log.debug("class renamed to {}", probe.getClassName()); } sendCommand(new RenameCommand(probe.getClassName())); } if (log.isDebugEnabled()) { log.debug("created BTraceRuntime instance for {}", probe.getClassName()); log.debug("sending Okay command"); } sendCommand(new StatusCommand()); // Warn about failed extensions Map failed = ExtensionRegistry.getFailedExtensions(); if (!failed.isEmpty()) { StringBuilder warning = new StringBuilder(); warning.append("[BTRACE WARN] ").append(failed.size()) .append(" extension(s) failed to load:\n"); for (Map.Entry entry : failed.entrySet()) { String simpleName = entry.getKey().substring(entry.getKey().lastIndexOf('.') + 1); warning.append(" - ").append(simpleName) .append(": ").append(entry.getValue()).append("\n"); } warning.append("Use 'btrace -le ' for details.\n"); sendCommand(new MessageCommand(warning.toString())); } // Expose extension-declared permissions for integration visibility // Print extension permissions only when explicitly requested (debug or system property) if (settings.isDebug() || Boolean.getBoolean("btrace.list.extension.permissions")) { try { ExtensionLoader loader = Main.getExtensionLoader(); if (loader != null) { StringBuilder info = new StringBuilder(); info.append("[BTRACE INFO] Extensions and declared permissions:\n"); for (ExtensionDescriptorDTO ext : loader.getAvailableExtensions()) { PermissionSet perms = ext.getRequiredPermissions(); String pStr = perms != null && !perms.isEmpty() ? perms.toString() : "[]"; info.append(" - ").append(ext.getId()).append(": ").append(pStr).append("\n"); } sendCommand(new MessageCommand(info.toString())); } } catch (Throwable t) { // ignore, informational only } } boolean entered = false; try { entered = BTraceRuntimeAccess.enter((BTraceRuntimeBridge) runtime); return probe.register(runtime, transformer); } catch (Throwable th) { log.debug("Failed to load BTrace probe", th); errorExit(th); return null; } finally { if (entered) { BTraceRuntime.leave(); } } } /** * Validates that all {@code @Injected} service field types used by the given probe are * declared by some available extension. This runs in the agent's runtime where the actual * classloader and JPMS module layer apply. * * Why reflection here (vs. pure ASM): * - Classloader identity: Ensures types are checked against the agent's classes loaded by the * correct loader. Name-only checks in ASM cannot detect split-brain issues (same FQN, different * loader/JAR) that would later cause ClassCastException. * - JPMS access rules: Surfaces missing exports/opens and other module access constraints that * cannot be proven by static bytecode analysis. * - Linkage/loadability: Fails fast if a referenced type is not actually resolvable on the * agent's runtime path (NoClassDefFoundError/missing transitive dependencies). * - Assignability truth: Verifies that the service type corresponds to something an extension * actually declares in its manifest, avoiding false positives from shaded or version-skewed * classes. * * Implementation notes: * - We use reflection only to access the probe's internal service field map (to avoid a direct * compile-time dependency on the probe's delegate type) and to keep the agent/probe boundary * clean. We do not instantiate user classes or trigger class initializers. * - This check complements compile-time and bytecode-time validation (ASM-based) which enforce * structural rules without loading classes. Reflection here provides the necessary runtime * assurance in the actual environment where the agent will operate. */ private void validateDeclaredServices(BTraceProbe probe) throws IOException { if (!(probe instanceof BTraceProbePersisted)) { return; } ExtensionLoader loader = Main.getExtensionLoader(); if (loader == null) { return; } // Reflectively access serviceFields() from the delegate to get injected service types try { java.lang.reflect.Field delF = BTraceProbePersisted.class.getDeclaredField("delegate"); delF.setAccessible(true); Object delegate = delF.get(probe); java.lang.reflect.Method svcM = delegate.getClass().getDeclaredMethod("serviceFields"); svcM.setAccessible(true); @SuppressWarnings("unchecked") Map svcMap = (Map) svcM.invoke(delegate); if (svcMap != null) { for (String internalName : svcMap.values()) { String fqcn = internalName.replace('/', '.'); if (loader.findExtensionForService(fqcn) == null) { throw new IOException( "Injected service type not declared by any extension: " + fqcn); } } } } catch (ReflectiveOperationException e) { log.debug("Unable to inspect injected services for validation", e); } } protected void closeAll() throws IOException { if (flusher != null) { flusher.cancel(); } if (out != null) { out.close(); } WRITER_MAP.remove(outputName); } private void errorExit(Throwable th) throws IOException { log.debug("sending error command"); sendCommand(new ErrorCommand(th)); log.debug("sending exit command"); sendCommand(new ExitCommand(1)); closeAll(); } private void cleanupTransformers() { if (probe != null) { String probeName = probe.getClassName(); probe.unregister(); // Drop the registry's strong reference to the BTraceRuntime.Impl created in // initialize() via BTraceRuntimes.getRuntime(probe.getClassName(), ...). Without // this, the registry keeps the Impl (and, transitively, the probe Class and // its per-probe ClassLoader) reachable forever, defeating probe class unloading. // Must use the same key that was used to register — here, the dotted class name. BTraceRuntimes.removeRuntime(probeName); } } // package privates below this point final boolean isInitialized() { return initialized; } final BTraceRuntime.Impl getRuntime() { return runtime; } final String getClassName() { return probe != null ? probe.getClassName() : ""; } private final boolean isCandidate(Class c) { String cname = c.getName().replace('.', '/'); if (c.isInterface() || c.isPrimitive() || c.isArray()) { return false; } if (ClassFilter.isSensitiveClass(cname)) { return false; } else { return probe.willInstrument(c); } } private final void startRetransformClasses(int numClasses) { sendCommand(new RetransformationStartNotification(numClasses)); if (log.isDebugEnabled()) { log.debug("calling retransformClasses ({} classes to be retransformed)", numClasses); } } final void endRetransformClasses() { sendCommand(new StatusCommand()); log.debug("finished retransformClasses"); } // Internals only below this point private BTraceProbe load(byte[] buf, ArgsMap args) { BTraceProbeFactory f = new BTraceProbeFactory(settings); log.debug("loading BTrace class"); BTraceProbe cn = f.createProbe(buf, args); if (cn != null) { if (cn.isVerified()) { if (log.isDebugEnabled()) { log.debug("loaded '{}' successfully", cn.getClassName()); } } else { if (log.isDebugEnabled()) { log.debug("{} failed verification", cn.getClassName()); } return null; } } return BTraceProbePersisted.from(cn); } boolean retransformLoaded() throws UnmodifiableClassException { if (runtime == null) { return false; } if (probe.isTransforming() && settings.isRetransformStartup()) { ArrayList> list = new ArrayList<>(); log.debug("retransforming loaded classes"); log.debug("filtering loaded classes"); ClassCache cc = ClassCache.getInstance(); for (Class c : inst.getAllLoadedClasses()) { if (c != null) { if (inst.isModifiableClass(c) && isCandidate(c)) { if (log.isDebugEnabled()) { log.debug("candidate {} added", c); } list.add(c); } } } list.trimToSize(); int size = list.size(); if (size > 0) { Class[] classes = new Class[size]; list.toArray(classes); startRetransformClasses(size); if (log.isDebugEnabled()) { for (Class c : classes) { try { log.debug("Attempting to retransform class: {}", c.getName()); inst.retransformClasses(c); } catch (ClassFormatError | VerifyError e) { // Avoid printing full stack traces in debug to keep target stderr clean log.debug("Class '{}' verification failed: {}", c.getName(), e.toString()); sendCommand( new MessageCommand( "[BTRACE WARN] Class verification failed: " + c.getName() + " (" + e.getMessage() + ")")); } } } else { try { inst.retransformClasses(classes); } catch (ClassFormatError | VerifyError e) { /* * If the en-block retransformation fails because of verification retry classes one-by-one. * Otherwise all classes are rolled back to the original state and no instrumentation * is applied. */ for (Class c : classes) { try { inst.retransformClasses(c); } catch (ClassFormatError | VerifyError e1) { // Avoid printing full stack traces in debug to keep target stderr clean log.debug("Class '{}' verification failed: {}", c.getName(), e1.toString()); sendCommand( new MessageCommand( "[BTRACE WARN] Class verification failed: " + c.getName() + " (" + e1.getMessage() + ")")); } } } } } } return true; } protected void sendCommand(Command command) { if (runtime == null) { log.warn("Cannot send command {}, runtime not initialized", command.getClass().getSimpleName()); return; } runtime.sendCommand(command); } static Client findClient(String uuid) { try { UUID id = UUID.fromString(uuid); return CLIENTS.get(id); } catch (IllegalArgumentException e) { return null; } } @Override public String toString() { return "BTrace Client: " + id + "[" + probe.getClassName() + "]"; } private static String formatPermissionError(Set missing) { StringBuilder sb = new StringBuilder(); sb.append("Probe requires permissions that are not granted:\n\n"); for (Permission p : missing) { sb.append(" - ").append(p.name()).append("\n"); sb.append(" ").append(p.getRiskDescription()).append("\n"); } sb.append("\nTo allow these permissions, use:\n"); sb.append(" --grant=") .append(missing.stream().map(Permission::name).collect(Collectors.joining(","))) .append("\n"); sb.append("\nOr use --grantAll=true to allow all permissions (not recommended).\n"); return sb.toString(); } } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/ClientContext.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import java.lang.instrument.Instrumentation; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.instr.BTraceTransformer; /** * Client-context data class * * @author Jaroslav Bachorik */ class ClientContext { private final Instrumentation instr; private final BTraceTransformer transformer; private final ArgsMap args; private final SharedSettings settings; ClientContext( Instrumentation instr, BTraceTransformer transformer, ArgsMap args, SharedSettings settings) { this.instr = instr; this.transformer = transformer; this.args = args; this.settings = settings; } Instrumentation getInstr() { return instr; } BTraceTransformer getTransformer() { return transformer; } SharedSettings getSettings() { return settings; } ArgsMap getArguments() { return args; } } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/FileClient.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLDecoder; import java.security.CodeSigner; import java.util.Arrays; import java.util.Enumeration; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.PrintableCommand; import org.openjdk.btrace.instr.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a local client communicated by trace file. The trace script is specified as a File of * a .class file or a byte array containing bytecode of the trace script. * * @author A. Sundararajan * @author J.Bachorik */ class FileClient extends Client { private static final Logger log = LoggerFactory.getLogger(FileClient.class); private final AtomicBoolean noOutputNotified = new AtomicBoolean(false); private boolean canLoadPack = true; FileClient(ClientContext ctx, File scriptFile) throws IOException { super(ctx); if (!init(readScript(scriptFile))) { log.warn("Unable to load BTrace script {}", scriptFile); } } private static byte[] readAll(InputStream is, long size) throws IOException { if (is == null) throw new NullPointerException(); byte[] buf = new byte[size != -1 ? Math.min((int) size, 512 * 1024 * 1024) : 8192]; int bufsize = buf.length; int off = 0; int read; while ((read = is.read(buf, off, bufsize - off)) > -1) { off += read; if (off >= bufsize) { buf = Arrays.copyOf(buf, bufsize * 2); bufsize = buf.length; } } return Arrays.copyOf(buf, off); } private boolean init(byte[] code) throws IOException { InstrumentCommand cmd = new InstrumentCommand(code, argsMap); boolean ret = loadClass(cmd) != null; if (ret) { initialize(); } return ret; } @SuppressWarnings("RedundantThrows") @Override public void onCommand(Command cmd) throws IOException { if (log.isDebugEnabled()) { log.debug("client {}: got {}", getClassName(), cmd); } switch (cmd.getType()) { case Command.EXIT: onExit(((ExitCommand) cmd).getExitCode()); break; default: if (cmd instanceof PrintableCommand) { if (out == null) { if (noOutputNotified.compareAndSet(false, true)) { log.debug("No output stream. DataCommand output is ignored."); } } else { ((PrintableCommand) cmd).print(out); out.flush(); } } break; } } private byte[] readScript(File file) throws IOException { String path = file.getPath(); if (path.startsWith(Constants.EMBEDDED_BTRACE_SECTION_HEADER)) { return settings.isTrusted() ? loadQuick(path) : loadWithSecurity(path); } else { int size = (int) file.length(); try (FileInputStream fis = new FileInputStream(file)) { return readAll(fis, size); } } } private byte[] loadQuick(String path) throws IOException { try (InputStream is = ClassLoader.getSystemResourceAsStream(path)) { return readAll(is, -1); } } private byte[] loadWithSecurity(String path) throws IOException { URL scriptUrl = ClassLoader.getSystemResource(path); if (scriptUrl.getProtocol().equals("jar")) { String jarPath = scriptUrl.getPath().substring(5, scriptUrl.getPath().indexOf("!")); JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8")); Enumeration ens = jar.entries(); while (ens.hasMoreElements()) { JarEntry en = ens.nextElement(); if (!en.isDirectory()) { if (en.toString().equals(path)) { byte[] data = readAll(jar.getInputStream(en), en.getSize()); CodeSigner[] signers = en.getCodeSigners(); canLoadPack = signers != null && signers.length != 0; return data; } } } } return null; } } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.DebugSupport; import org.openjdk.btrace.core.Messages; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.core.comm.ErrorCommand; import org.openjdk.btrace.core.comm.StatusCommand; import org.openjdk.btrace.core.comm.WireIO; import org.openjdk.btrace.extension.ExtensionLoader; import org.openjdk.btrace.extension.impl.ExtensionBridgeImpl; import org.openjdk.btrace.instr.BTraceProbeFactory; import org.openjdk.btrace.instr.BTraceTransformer; import org.openjdk.btrace.instr.Constants; import org.openjdk.btrace.runtime.BTraceRuntimes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.jar.JarFile; import java.util.regex.Pattern; import java.util.zip.ZipFile; import static org.openjdk.btrace.core.Args.ALLOW_EXTENSIONS; import static org.openjdk.btrace.core.Args.ALLOW_PRIVILEGED; import static org.openjdk.btrace.core.Args.BOOT_CLASS_PATH; import static org.openjdk.btrace.core.Args.CMD_QUEUE_LIMIT; import static org.openjdk.btrace.core.Args.CONFIG; import static org.openjdk.btrace.core.Args.DEBUG; import static org.openjdk.btrace.core.Args.DENY; import static org.openjdk.btrace.core.Args.DENY_EXTENSIONS; import static org.openjdk.btrace.core.Args.DUMP_CLASSES; import static org.openjdk.btrace.core.Args.DUMP_DIR; import static org.openjdk.btrace.core.Args.FILE_ROLL_MAX_ROLLS; import static org.openjdk.btrace.core.Args.FILE_ROLL_MILLISECONDS; import static org.openjdk.btrace.core.Args.GRANT; import static org.openjdk.btrace.core.Args.GRANT_ALL; import static org.openjdk.btrace.core.Args.HELP; import static org.openjdk.btrace.core.Args.LIBS; import static org.openjdk.btrace.core.Args.NO_SERVER; import static org.openjdk.btrace.core.Args.PORT; import static org.openjdk.btrace.core.Args.PROBE_DESC_PATH; import static org.openjdk.btrace.core.Args.SCRIPT; import static org.openjdk.btrace.core.Args.SCRIPT_DIR; import static org.openjdk.btrace.core.Args.SCRIPT_OUTPUT_DIR; import static org.openjdk.btrace.core.Args.SCRIPT_OUTPUT_FILE; import static org.openjdk.btrace.core.Args.STARTUP_RETRANSFORM; import static org.openjdk.btrace.core.Args.STATSD; import static org.openjdk.btrace.core.Args.STDOUT; import static org.openjdk.btrace.core.Args.SYSTEM_CLASS_PATH; import static org.openjdk.btrace.core.Args.TRACK_RETRANSFORMS; import static org.openjdk.btrace.core.Args.TRUSTED; /** * This is the main class for BTrace java.lang.instrument agent. * * @author A. Sundararajan * @author Joachim Skeie (rolling output) */ @SuppressWarnings("RedundantThrows") public final class Main { public static final int BTRACE_DEFAULT_PORT = 2020; private static final boolean AGENT_DEBUG = Boolean.getBoolean("btrace.agent.debug"); private static final Pattern KV_PATTERN = Pattern.compile(","); private static final SharedSettings settings = SharedSettings.GLOBAL; private static final BTraceTransformer transformer = new BTraceTransformer(new DebugSupport(settings)); // #BTRACE-42: Non-daemon thread prevents traced application from exiting private static final ThreadFactory qProcessorThreadFactory = r -> { Thread result = new Thread(r, "BTrace Command Queue Processor"); result.setDaemon(true); return result; }; private static final ExecutorService serializedExecutor = Executors.newSingleThreadExecutor(qProcessorThreadFactory); private static final long ts = System.nanoTime(); private static volatile ArgsMap argMap; private static volatile Instrumentation inst; private static volatile Long fileRollMilliseconds; private static volatile ExtensionLoader extensionLoader; private static volatile boolean serverRunning = true; private static ServerSocket serverSocket; private static final Logger log = LoggerFactory.getLogger(Main.class); public static void premain(String args, Instrumentation inst) { main(args, inst); } public static void agentmain(String args, Instrumentation inst) { main(args, inst); } private static synchronized void main(String args, Instrumentation inst) { if (AGENT_DEBUG) System.err.println("[BTrace Agent] Initialization started"); if (Main.inst != null) { if (AGENT_DEBUG) System.err.println("[BTrace Agent] Agent already initialized, skipping"); return; } else { Main.inst = inst; } try { if (AGENT_DEBUG) System.err.println("[BTrace Agent] Loading arguments"); loadArgs(args); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Arguments loaded"); boolean isDebug = Boolean.parseBoolean(argMap.get(DEBUG)); // set the debug level based on cmdline config settings.setDebug(isDebug); DebugSupport.initLoggers(isDebug, log); // Load defaults from file-based permission policy first if (AGENT_DEBUG) System.err.println("[BTrace Agent] Loading permission policy"); org.openjdk.btrace.extension.PermissionPolicy.get().loadFromDefaults(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Permission policy loaded"); // Then parse and apply agent args (which override file policy) if (AGENT_DEBUG) System.err.println("[BTrace Agent] Parsing arguments"); parseArgs(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Arguments parsed"); // settings are all built-up; set the logging system properties accordingly DebugSupport.initLoggers(settings.isDebug(), log); String tmp = argMap.get(NO_SERVER); // noServer is defaulting to true if startup scripts are defined boolean noServer = tmp != null ? Boolean.parseBoolean(tmp) : hasScripts(); Thread agentThread = null; if (noServer) { log.debug("noServer is true, server not started"); } else { agentThread = new Thread( () -> { BTraceRuntime.enter(); try { startServer(); } finally { BTraceRuntime.leave(); } }); } // set the fall-back instrumentation object to BTraceRuntime if (AGENT_DEBUG) System.err.println("[BTrace Agent] Setting up runtime"); BTraceRuntime.instrumentation = inst; // force back-registration of BTraceRuntimeImpl in BTraceRuntime if (AGENT_DEBUG) System.err.println("[BTrace Agent] Initializing BTraceRuntimes"); BTraceRuntimes.getDefault(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] BTraceRuntimes initialized"); // ensure runtime accessor is registered if (AGENT_DEBUG) System.err.println("[BTrace Agent] Registering runtime accessor"); BTraceRuntimes.ensureAccessorRegistered(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Runtime accessor registered"); // init BTraceRuntime if (AGENT_DEBUG) System.err.println("[BTrace Agent] Initializing unsafe"); BTraceRuntime.initUnsafe(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Unsafe initialized"); // initialize extension system if (AGENT_DEBUG) System.err.println("[BTrace Agent] Initializing extensions"); initExtensions(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Extensions initialized"); if (agentThread != null) { BTraceRuntime.enter(); try { agentThread.setDaemon(true); log.debug("starting agent thread"); agentThread.start(); } finally { BTraceRuntime.leave(); } } if (AGENT_DEBUG) System.err.println("[BTrace Agent] Adding class transformer"); log.debug("Adding class transformer"); inst.addTransformer(transformer, true); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Transformer added"); try { // the MethodHandleNatives must be instrumented to track start-end of indy linking to avoid deadlocking if (AGENT_DEBUG) System.err.println("[BTrace Agent] Instrumenting MethodHandleNatives"); Class clz = ClassLoader.getSystemClassLoader().loadClass("java.lang.invoke.MethodHandleNatives"); inst.retransformClasses(clz); if (AGENT_DEBUG) System.err.println("[BTrace Agent] MethodHandleNatives instrumented"); } catch (Throwable t) { if (AGENT_DEBUG) System.err.println("[BTrace Agent] Failed to instrument MethodHandleNatives: " + t.getMessage()); log.debug("Failed to instrument MethodHandleNatives", t); } if (AGENT_DEBUG) System.err.println("[BTrace Agent] Starting scripts"); int startedScripts = startScripts(); if (AGENT_DEBUG) System.err.println("[BTrace Agent] Initialization complete, " + startedScripts + " scripts started"); } catch (Throwable t) { // FATAL errors should always be printed System.err.println("[BTrace Agent] FATAL: Initialization failed: " + t.getClass().getName() + ": " + t.getMessage()); t.printStackTrace(System.err); log.error("Failed to initialize BTrace agent", t); throw new RuntimeException("BTrace agent initialization failed", t); } finally { log.debug("Agent init took: {}", (System.nanoTime() - ts) + "ns"); } } private static boolean hasScripts() { return argMap.containsKey(SCRIPT) || argMap.containsKey(SCRIPT_DIR); } private static final class LogValue { final String logLine; final Throwable throwable; public LogValue(String logLine, Throwable throwable) { this.logLine = logLine; this.throwable = throwable; } } private static void loadDefaultArguments(String config) { try { String propTarget = Constants.EMBEDDED_BTRACE_SECTION_HEADER + "agent.properties"; InputStream is = ClassLoader.getSystemResourceAsStream(propTarget); if (is != null) { Properties ps = new Properties(); ps.load(is); StringBuilder logMsg = new StringBuilder(); for (Map.Entry entry : ps.entrySet()) { String keyConfig = ""; String argKey = (String) entry.getKey(); int configPos = argKey.lastIndexOf('#'); if (configPos > -1) { keyConfig = argKey.substring(0, configPos); argKey = argKey.substring(configPos + 1); } if (config == null || keyConfig.isEmpty() || config.equals(keyConfig)) { String argVal = (String) entry.getValue(); switch (argKey) { case SCRIPT: { // special treatment for the 'script' parameter boolean replace = false; String scriptVal = argVal; if (scriptVal.startsWith("!")) { scriptVal = scriptVal.substring(1); replace = true; } else { String oldVal = argMap.get(argKey); if (oldVal != null && !oldVal.isEmpty()) { scriptVal = oldVal + ":" + scriptVal; } else { replace = true; } } if (replace) { logMsg .append("setting default agent argument '") .append(argKey) .append("' to '") .append(scriptVal) .append("'\n"); } else { logMsg .append("augmenting default agent argument '") .append(argKey) .append("':'") .append(argMap.get(argKey)) .append("' with '") .append(argVal) .append("'\n"); } argMap.put(argKey, scriptVal); break; } case SYSTEM_CLASS_PATH: // fall through case BOOT_CLASS_PATH: // fall through case CONFIG: { logMsg.append("argument '").append(argKey).append("' is not overridable\n"); break; } default: { if (!argMap.containsKey(argKey)) { logMsg .append("applying default agent argument '") .append(argKey) .append("'='") .append(argVal) .append("'\n"); argMap.put(argKey, argVal); } } } } } DebugSupport.initLoggers(Boolean.parseBoolean(argMap.get(DEBUG)), log); if (log.isDebugEnabled()) { log.debug(logMsg.toString()); } } } catch (IOException e) { if (log.isDebugEnabled()) { log.debug(e.toString(), e); } } } /** * Initialize the extension system by discovering and loading extensions * from configured extension directories. */ private static void initExtensions() { try { log.info("Initializing BTrace extension system"); // Determine BTRACE_HOME from agent JAR location String btraceHome = getBTraceHome(); if (log.isDebugEnabled()) { log.debug("BTRACE_HOME={}", btraceHome); } if (btraceHome == null) { log.warn("Could not determine BTRACE_HOME, extensions will not be loaded"); System.err.println("[DEBUG] BTRACE_HOME is null, skipping extension initialization"); return; } // Initialize extension loader with boot classloader as parent, configuration, and instrumentation ClassLoader bootClassLoader = Main.class.getClassLoader(); extensionLoader = ExtensionLoader.initialize(btraceHome, bootClassLoader, inst); // Initialize invokedynamic bridge for extensions after loader is ready ExtensionBridgeImpl.initialize(extensionLoader); } catch (Exception e) { log.error("Failed to initialize extension system: {}", e.getMessage(), e); } } /** * Get BTRACE_HOME directory by locating the agent JAR. * * @return BTRACE_HOME path, or null if not found */ private static String getBTraceHome() { try { // Get the agent JAR location String agentPath = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath(); // Agent is typically at BTRACE_HOME/libs/btrace-agent.jar or BTRACE_HOME/libs/btrace.jar File agentJar = new File(agentPath); String jarName = agentJar.getName(); if (agentJar.exists() && (jarName.equals("btrace-agent.jar") || jarName.equals("btrace.jar"))) { File libsDir = agentJar.getParentFile(); if (libsDir != null && libsDir.getName().equals("libs")) { File btraceHome = libsDir.getParentFile(); if (btraceHome != null) { return btraceHome.getAbsolutePath(); } } } // Try BTRACE_HOME environment variable String envHome = System.getenv("BTRACE_HOME"); if (envHome != null && new File(envHome).exists()) { return envHome; } } catch (Exception e) { log.debug("Failed to determine BTRACE_HOME: {}", e.getMessage()); } return null; } /** * Get the extension loader instance. * * @return extension loader, or null if not initialized */ public static ExtensionLoader getExtensionLoader() { return extensionLoader; } private static int startScripts() { int scriptCount = 0; String p = argMap.get(STDOUT); boolean traceToStdOut = p != null && !"false".equals(p); if (log.isDebugEnabled()) { log.debug("stdout is {}", traceToStdOut); } List scripts = locateScripts(argMap); for (String script : scripts) { if (loadBTraceScript(script, traceToStdOut)) { scriptCount++; } } return scriptCount; } static List locateScripts(ArgsMap argsMap) { String script = argsMap.get(SCRIPT); String scriptDir = argsMap.get(SCRIPT_DIR); List scripts = new ArrayList<>(); if (script != null) { StringTokenizer tokenizer = new StringTokenizer(script, ":"); if (log.isDebugEnabled()) { log.debug( ((tokenizer.countTokens() == 1) ? "initial script is {}" : "initial scripts are {}"), script); } while (tokenizer.hasMoreTokens()) { scripts.add(tokenizer.nextToken()); } } if (scriptDir != null) { File dir = new File(scriptDir); if (dir.isDirectory()) { if (log.isDebugEnabled()) { log.debug("found scriptdir: {}", dir.getAbsolutePath()); } File[] files = dir.listFiles(); if (files != null) { for (File file : files) { scripts.add(file.getAbsolutePath()); } } } } return scripts; } private static void usage() { System.out.println(Messages.get("btrace.agent.usage")); System.exit(0); } private static void loadArgs(String args) { if (args == null) { args = ""; } String[] pairs = KV_PATTERN.split(args); argMap = new ArgsMap(); for (String s : pairs) { int i = s.indexOf('='); String key, value = ""; if (i != -1) { key = s.substring(0, i).trim(); if (i + 1 < s.length()) { value = s.substring(i + 1).trim(); } } else { key = s; } argMap.put(key, value); } } private static void parseArgs() { String p = argMap.get(HELP); if (p != null) { usage(); } String libs = argMap.get(LIBS); String config = argMap.get(CONFIG); processClasspaths(libs); loadDefaultArguments(config); p = argMap.get(DEBUG); settings.setDebug(p != null && !"false".equals(p)); DebugSupport.initLoggers(settings.isDebug(), log); log.debug("debugMode is {}", settings.isDebug()); for (Map.Entry e : argMap) { String key = e.getKey(); p = e.getValue(); switch (key) { case STARTUP_RETRANSFORM: { if (!p.isEmpty()) { settings.setRetransformStartup(Boolean.parseBoolean(p)); log.debug(STARTUP_RETRANSFORM + " is {}", settings.isRetransformStartup()); } break; } case DUMP_DIR: { String dumpClassesVal = argMap.get(DUMP_CLASSES); if (dumpClassesVal != null) { boolean dumpClasses = Boolean.parseBoolean(dumpClassesVal); log.debug(DUMP_CLASSES + " is {}", dumpClasses); if (dumpClasses) { String dumpDir = argMap.get(DUMP_DIR); settings.setDumpDir(dumpDir != null ? dumpDir : "."); if (isDebug()) { log.debug(DUMP_DIR + " is {}", dumpDir); } } } break; } case CMD_QUEUE_LIMIT: { if (!p.isEmpty()) { System.setProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, p); log.debug(CMD_QUEUE_LIMIT + " provided: {}", p); } break; } case TRACK_RETRANSFORMS: { if (!p.isEmpty()) { settings.setTrackRetransforms(Boolean.parseBoolean(p)); if (settings.isTrackRetransforms()) { log.debug(TRACK_RETRANSFORMS + " is on"); } } break; } case SCRIPT_OUTPUT_FILE: { if (!p.isEmpty()) { settings.setOutputFile(p); log.debug(SCRIPT_OUTPUT_FILE + " is {}", p); } break; } case SCRIPT_OUTPUT_DIR: { if (!p.isEmpty()) { settings.setScriptOutputDir(p); log.debug(SCRIPT_OUTPUT_DIR + " is {}", p); } break; } case FILE_ROLL_MILLISECONDS: { if (!p.isEmpty()) { Long msParsed = null; try { msParsed = Long.parseLong(p); fileRollMilliseconds = msParsed; } catch (NumberFormatException nfe) { fileRollMilliseconds = null; } if (fileRollMilliseconds != null) { settings.setFileRollMilliseconds(fileRollMilliseconds.intValue()); log.debug(FILE_ROLL_MILLISECONDS + " is {}", fileRollMilliseconds); } } break; } case FILE_ROLL_MAX_ROLLS: { if (!p.isEmpty()) { Integer rolls = null; try { rolls = Integer.parseInt(p); } catch (NumberFormatException ignored) { // ignore } if (rolls != null) { settings.setFileRollMaxRolls(rolls); } } break; } case TRUSTED: { if (!p.isEmpty()) { settings.setTrusted(Boolean.parseBoolean(p)); log.debug("trustedMode is {}", settings.isTrusted()); } break; } case STATSD: { if (!p.isEmpty()) { String[] parts = p.split(":"); if (parts.length == 2) { settings.setStatsdHost(parts[0].trim()); try { settings.setStatsdPort(Integer.parseInt(parts[1].trim())); } catch (NumberFormatException ex) { log.warn("Invalid statsd port number: {}", parts[1]); // leave the port unconfigured } } else if (parts.length == 1) { settings.setStatsdHost(parts[0].trim()); } } break; } case PROBE_DESC_PATH: { settings.setProbeDescPath(!p.isEmpty() ? p : "."); log.debug("probe descriptor path is {}", settings.getProbeDescPath()); break; } case BOOT_CLASS_PATH: { settings.setBootClassPath(!p.isEmpty() ? p : ""); log.debug("probe boot class path is {}", settings.getBootClassPath()); break; } case GRANT: { if (!p.isEmpty()) { settings.setGrantedPermissions(SharedSettings.parsePermissions(p)); log.debug("granted permissions: {}", settings.getGrantedPermissions()); } break; } case DENY: { if (!p.isEmpty()) { settings.setDeniedPermissions(SharedSettings.parsePermissions(p)); log.debug("denied permissions: {}", settings.getDeniedPermissions()); } break; } case GRANT_ALL: { if (!p.isEmpty()) { settings.setGrantAll(Boolean.parseBoolean(p)); log.debug("grantAll: {}", settings.isGrantAll()); } break; } case ALLOW_EXTENSIONS: { if (!p.isEmpty()) { org.openjdk.btrace.extension.PermissionPolicy.get().setAllowExtensionsCsv(p); log.debug("allowExtensions: {}", p); } break; } case DENY_EXTENSIONS: { if (!p.isEmpty()) { org.openjdk.btrace.extension.PermissionPolicy.get().setDenyExtensionsCsv(p); log.debug("denyExtensions: {}", p); } break; } case ALLOW_PRIVILEGED: { if (!p.isEmpty()) { org.openjdk.btrace.extension.PermissionPolicy.get().setAllowPrivileged(Boolean.parseBoolean(p)); log.debug("allowPrivileged: {}", p); } break; } default: { if (key.startsWith("$")) { String pKey = key.substring(1); System.setProperty(pKey, p); log.debug("Setting system property: {}={}", pKey, p); } } } } } private static void processClasspaths(String libs) { // Try to find JAR via Loader.class (unmasked bootstrap class) // Main.class won't work because it's loaded from .classdata String bootPath = null; try { Class loaderClass = Class.forName("org.openjdk.btrace.boot.Loader"); URL loaderResource = loaderClass.getResource("Loader.class"); if (loaderResource != null) { bootPath = loaderResource.toString(); if (bootPath.startsWith("jar:file:")) { // Extract JAR path from jar:file:/path/to/btrace.jar!/org/openjdk/btrace/boot/Loader.class bootPath = bootPath.substring("jar:file:".length()); int idx = bootPath.indexOf("!"); if (idx > -1) { bootPath = bootPath.substring(0, idx); } } } } catch (ClassNotFoundException e) { // Fall back to Main.class if Loader not found (shouldn't happen) URL agentJar = Main.class.getResource("Main.class"); if (agentJar != null) { bootPath = agentJar.toString().replace("jar:file:", ""); int idx = bootPath.indexOf("btrace-agent.jar"); if (idx > -1) { bootPath = bootPath.substring(0, idx) + "btrace-boot.jar"; } } } String bootClassPath = argMap.get(BOOT_CLASS_PATH); if (bootClassPath == null && bootPath != null) { bootClassPath = bootPath; } else if (bootClassPath != null && bootPath != null) { if (".".equals(bootClassPath)) { bootClassPath = bootPath; } else { bootClassPath = bootPath + File.pathSeparator + bootClassPath; } } log.debug("Bootstrap ClassPath: {}", bootClassPath); StringTokenizer tokenizer = new StringTokenizer(bootClassPath, File.pathSeparator); try { while (tokenizer.hasMoreTokens()) { String path = tokenizer.nextToken(); File f = new File(path); if (!f.exists()) { log.debug("BTrace bootstrap classpath resource [{}] does not exist", path); } else { if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) { JarFile jf = asJarFile(f); log.debug("Adding jar: {}", jf); inst.appendToBootstrapClassLoaderSearch(jf); } else { log.debug("ignoring boot classpath element '{}' - only jar files allowed", path); } } } } catch (IOException ex) { log.debug("adding to boot classpath failed!", ex); return; } String systemClassPath = argMap.get(SYSTEM_CLASS_PATH); if (systemClassPath != null) { log.debug("System ClassPath: {}", systemClassPath); tokenizer = new StringTokenizer(systemClassPath, File.pathSeparator); try { while (tokenizer.hasMoreTokens()) { String path = tokenizer.nextToken(); File f = new File(path); if (!f.exists()) { log.debug("BTrace system classpath resource [{}] does not exist.", path); } else { if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) { JarFile jf = asJarFile(f); inst.appendToSystemClassLoaderSearch(jf); } else { log.debug("ignoring system classpath element '{}' - only jar files allowed", path); } } } } catch (IOException ex) { log.debug("adding to boot classpath failed!", ex); return; } } addPreconfLibs(libs); } @SuppressWarnings("JavaReflectionMemberAccess") private static JarFile asJarFile(File path) throws IOException { try { Class.forName("java.lang.Module"); // bail out early if on pre Java 9 version Class rtClass = Runtime.class; Method m = rtClass.getMethod("version"); Object version = m.invoke(null); // JPMS enabled version of JarFile has different constructor signature return JarFile.class .getConstructor(File.class, boolean.class, int.class, version.getClass()) .newInstance(path, true, ZipFile.OPEN_READ, version); } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException ignore) { } return new JarFile(path); } private static void addPreconfLibs(String libs) { URL u = Main.class.getClassLoader().getResource(Main.class.getName().replace('.', '/') + ".class"); if (u != null) { String path = u.toString(); int delimiterPos = path.lastIndexOf('!'); if (delimiterPos > -1) { String jar = path.substring(9, delimiterPos); File jarFile = new File(jar); Path libRoot = new File(jarFile.getParent() + File.separator + "btrace-libs").toPath(); Path libFolder = libs != null ? libRoot.resolve(libs) : libRoot; if (Files.exists(libFolder)) { appendToBootClassPath(libFolder); appendToSysClassPath(libFolder); } else { if (libs != null && !libs.isEmpty()) { log.warn( "Invalid 'libs' configuration [{}]. Path '{}' does not exist.", libs, libFolder.toAbsolutePath()); } } } } } private static void appendToBootClassPath(Path libFolder) { Path bootLibs = libFolder.resolve("boot"); if (Files.exists(bootLibs)) { try { Files.walkFileTree( bootLibs, new FileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.toString().toLowerCase().endsWith(".jar")) { if (log.isDebugEnabled()) { log.debug("Adding {} to bootstrap classpath", file); } inst.appendToBootstrapClassLoaderSearch(new JarFile(file.toFile())); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } catch (IOException e) { log.debug("Failed to enhance bootstrap classpath", e); } } } private static void appendToSysClassPath(Path libFolder) { Path sysLibs = libFolder.resolve("system"); if (Files.exists(sysLibs)) { try { Files.walkFileTree( sysLibs, new FileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.toString().toLowerCase().endsWith(".jar")) { if (log.isDebugEnabled()) { log.debug("Adding {} to system classpath", file); } inst.appendToSystemClassLoaderSearch(new JarFile(file.toFile())); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } catch (IOException e) { log.debug("Failed to enhance sytem classpath", e); } } } private static boolean loadBTraceScript(String filePath, boolean traceToStdOut) { if (!BTraceProbeFactory.canLoad(filePath)) { return false; } try { String scriptName = ""; String scriptParent = ""; File traceScript = new File(filePath); scriptName = traceScript.getName(); scriptParent = traceScript.getParent(); if (!traceScript.exists()) { traceScript = new File(Constants.EMBEDDED_BTRACE_SECTION_HEADER + filePath); } if (scriptName.endsWith(".java")) { if (log.isDebugEnabled()) { log.debug("refusing {} - script should be a pre-compiled class file", filePath); } return false; } SharedSettings clientSettings = new SharedSettings(); clientSettings.from(settings); clientSettings.setClientName(scriptName); if (traceToStdOut) { clientSettings.setOutputFile("::stdout"); } else { String traceOutput = clientSettings.getOutputFile(); String outDir = clientSettings.getScriptOutputDir(); if (traceOutput == null || traceOutput.isEmpty()) { clientSettings.setOutputFile("${client}-${agent}.${ts}.btrace[default]"); if (outDir == null || outDir.isEmpty()) { clientSettings.setScriptOutputDir(scriptParent); } } } ClientContext ctx = new ClientContext(inst, transformer, argMap, clientSettings); Client client = new FileClient(ctx, traceScript); if (client.isInitialized()) { handleNewClient(client).get(); return true; } } catch (NullPointerException e) { if (log.isDebugEnabled()) { log.debug("script {} does not exist!", filePath, e); } } catch (RuntimeException | IOException | ExecutionException re) { if (log.isDebugEnabled()) { log.debug("Failed to load BTrace script {}", filePath, re); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return false; } // -- Internals only below this point @SuppressWarnings("InfiniteLoopStatement") private static void startServer() { int port = BTRACE_DEFAULT_PORT; String p = argMap.get(PORT); if (p != null) { try { port = Integer.parseInt(p); } catch (NumberFormatException exp) { error("invalid port assuming default.."); } } try { if (log.isDebugEnabled()) { log.debug("starting server at port {}", port); } System.setProperty("btrace.wireio", String.valueOf(WireIO.VERSION)); String scriptOutputFile = settings.getOutputFile(); if (scriptOutputFile != null && !scriptOutputFile.isEmpty()) { System.setProperty("btrace.output", scriptOutputFile); } serverSocket = new ServerSocket(port); System.setProperty("btrace.port", String.valueOf(serverSocket.getLocalPort())); // Add shutdown hook to close server socket on JVM exit Runtime.getRuntime() .addShutdownHook( new Thread( () -> { serverRunning = false; if (serverSocket != null && !serverSocket.isClosed()) { try { serverSocket.close(); log.debug("BTrace server socket closed"); } catch (IOException e) { log.debug("Error closing server socket", e); } } }, "BTrace Server Shutdown")); } catch (IOException ioexp) { log.error("Failed to start BTrace server on port {}", port, ioexp); return; } while (serverRunning) { try { log.debug("waiting for clients"); Socket sock = serverSocket.accept(); if (log.isDebugEnabled()) { log.debug("client accepted {}", sock); } ClientContext ctx = new ClientContext(inst, transformer, argMap, settings); Client client = RemoteClient.getClient(ctx, sock, Main::handleNewClient); } catch (RuntimeException | IOException re) { if (serverRunning) { if (log.isDebugEnabled()) { log.debug("BTrace server failed", re); } } } } } private static Future handleNewClient(Client client) { return serializedExecutor.submit( () -> { try { boolean entered = BTraceRuntime.enter(); try { if (log.isDebugEnabled()) { log.debug("new Client created {}", client); } if (client.retransformLoaded()) { client.getRuntime().sendCommand(new StatusCommand((byte) 1)); } } catch (UnmodifiableClassException uce) { log.debug("BTrace class retransformation failed", uce); client.getRuntime().sendCommand(new ErrorCommand(uce)); client.getRuntime().sendCommand(new StatusCommand(-1 * StatusCommand.STATUS_FLAG)); } finally { if (entered) { BTraceRuntime.leave(); } } } catch (Throwable t) { log.warn("Unhandled exception in client handler", t); } }); } private static void error(String msg) { System.err.println("btrace ERROR: " + msg); } private static boolean isDebug() { return settings.isDebug(); } } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/PerfReaderImpl.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import java.net.URISyntaxException; import org.openjdk.btrace.runtime.PerfReader; import sun.jvmstat.monitor.IntegerMonitor; import sun.jvmstat.monitor.LongMonitor; import sun.jvmstat.monitor.Monitor; import sun.jvmstat.monitor.MonitorException; import sun.jvmstat.monitor.MonitoredHost; import sun.jvmstat.monitor.MonitoredVm; import sun.jvmstat.monitor.StringMonitor; import sun.jvmstat.monitor.VmIdentifier; final class PerfReaderImpl implements PerfReader { private volatile MonitoredVm thisVm; private synchronized MonitoredVm getThisVm() { if (thisVm == null) { try { MonitoredHost localHost = MonitoredHost.getMonitoredHost("localhost"); VmIdentifier vmIdent = new VmIdentifier("0"); thisVm = localHost.getMonitoredVm(vmIdent); } catch (MonitorException | URISyntaxException me) { throw new IllegalArgumentException("jvmstat perf counters not available: " + me); } } return thisVm; } private Monitor findByName(String name) { try { return getThisVm().findByName(name); } catch (Exception e) { throw new RuntimeException(e); } } @Override public int perfInt(String name) { Monitor mon = findByName(name); if (mon == null) { throw new IllegalArgumentException("no such counter: " + name); } if (mon instanceof IntegerMonitor) { return ((IntegerMonitor) mon).intValue(); } else if (mon instanceof LongMonitor) { return (int) ((LongMonitor) mon).longValue(); } else { throw new IllegalArgumentException(name + " is not an int"); } } @Override public long perfLong(String name) { Monitor mon = findByName(name); if (mon == null) { throw new IllegalArgumentException("no such counter: " + name); } if (mon instanceof LongMonitor) { return ((LongMonitor) mon).longValue(); } else { throw new IllegalArgumentException(name + " is not a long"); } } @Override public String perfString(String name) { Monitor mon = findByName(name); if (mon == null) { throw new IllegalArgumentException("no such counter: " + name); } if (mon instanceof StringMonitor) { return ((StringMonitor) mon).stringValue(); } else { throw new IllegalArgumentException(name + " is not a string"); } } } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.net.Socket; import java.net.SocketException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; import org.openjdk.btrace.core.*; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.DisconnectCommand; import org.openjdk.btrace.core.comm.EventCommand; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.BinaryWireProtocol; import org.openjdk.btrace.core.comm.JavaSerializationProtocol; import org.openjdk.btrace.core.comm.ListFailedExtensionsCommand; import org.openjdk.btrace.core.comm.ListProbesCommand; import org.openjdk.btrace.core.comm.PrintableCommand; import org.openjdk.btrace.core.comm.ProtocolConfig; import org.openjdk.btrace.core.comm.ProtocolNegotiator; import org.openjdk.btrace.core.comm.ProtocolVersion; import org.openjdk.btrace.core.comm.ReconnectCommand; import org.openjdk.btrace.core.comm.SetSettingsCommand; import org.openjdk.btrace.core.comm.StatusCommand; import org.openjdk.btrace.core.comm.WireProtocol; import org.openjdk.btrace.extension.ExtensionRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a remote client communicated by socket. * * @author A. Sundararajan */ @SuppressWarnings({"SynchronizeOnNonFinalField", "SynchronizationOnLocalVariableOrMethodParameter"}) class RemoteClient extends Client { private static final Logger log = LoggerFactory.getLogger(RemoteClient.class); private final class DelayedCommandExecutor implements Function { private final boolean isConnected; public DelayedCommandExecutor(boolean isConnected) { this.isConnected = isConnected; } @Override public Boolean apply(Command value) { return dispatchCommand(value, isConnected); } } private volatile Socket sock; private volatile WireProtocol protocol; private volatile boolean disconnected = false; private final AtomicReferenceFieldUpdater sockUpdater = AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, Socket.class, "sock"); private final AtomicReferenceFieldUpdater protocolUpdater = AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, WireProtocol.class, "protocol"); private final CircularBuffer delayedCommands = new CircularBuffer<>(5000); static Client getClient(ClientContext ctx, Socket sock, Function> initCallback) throws IOException { SharedSettings settings = ctx.getSettings(); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); InputStream rawInput = sock.getInputStream(); OutputStream output = sock.getOutputStream(); ProtocolNegotiator negotiator = new ProtocolNegotiator(config.getVersion()); PushbackInputStream input = ProtocolNegotiator.createNegotiationStream(rawInput); ProtocolVersion negotiated; if (config.isAutoNegotiate()) { negotiated = negotiator.negotiateAgent(input, output); } else if (config.getVersion() == ProtocolVersion.V2) { negotiated = negotiator.negotiateAgent(input, output); if (negotiated != ProtocolVersion.V2) { throw new IOException("Protocol negotiation failed: expected V2"); } } else { negotiated = ProtocolVersion.V1; } WireProtocol wireProtocol = negotiated == ProtocolVersion.V2 ? new BinaryWireProtocol(input, output) : new JavaSerializationProtocol(input, output); while (true) { Command cmd; try { cmd = wireProtocol.read(); } catch (ClassNotFoundException e) { throw new IOException(e); } switch (cmd.getType()) { case Command.SET_PARAMS: { settings.from(((SetSettingsCommand) cmd).getParams()); break; } case Command.INSTRUMENT: { log.debug("got instrument command"); try { Client client = new RemoteClient(ctx, wireProtocol, sock, (InstrumentCommand) cmd); initCallback.apply(client).get(); client.sendCommand(new StatusCommand(StatusCommand.STATUS_FLAG)); return client; } catch (ExecutionException | InterruptedException e) { wireProtocol.write(new StatusCommand(-1 * StatusCommand.STATUS_FLAG)); throw new IOException(e); } } case Command.RECONNECT: { String probeId = ((ReconnectCommand) cmd).getProbeId(); log.debug("Attempting to reconnect client for probe {}", probeId); Client client = Client.findClient(probeId); log.debug("Found client {}", client); if (client instanceof RemoteClient) { ((RemoteClient) client).reconnect(wireProtocol, sock); client.sendCommand(new StatusCommand(ReconnectCommand.STATUS_FLAG)); return client; } wireProtocol.write(new StatusCommand(-1 * ReconnectCommand.STATUS_FLAG)); throw new IOException("Can not reconnect to non-remote session"); } case Command.LIST_PROBES: { ListProbesCommand listProbesCommand = (ListProbesCommand) cmd; listProbesCommand.setProbes(Client.listProbes()); wireProtocol.write(listProbesCommand); break; } case Command.LIST_FAILED_EXTENSIONS: { ListFailedExtensionsCommand listFailedCmd = (ListFailedExtensionsCommand) cmd; listFailedCmd.setFailedExtensions(ExtensionRegistry.getFailedExtensions()); wireProtocol.write(listFailedCmd); break; } case Command.EXIT: { return null; } default: { throw new IOException( "expecting instrument, reconnect or settings command! (" + cmd.getClass() + ")"); } } } } private RemoteClient( ClientContext ctx, WireProtocol protocol, Socket sock, InstrumentCommand cmd) throws IOException { super(ctx); this.sock = sock; this.protocol = protocol; this.settings.from(ctx.getSettings()); Class btraceClazz = loadClass(cmd); if (btraceClazz == null) { throw new RuntimeException("can not load BTrace class"); } initClient(); } private void initClient() { BTraceRuntime.initUnsafe(); Thread cmdHandler = new Thread( () -> { try { BTraceRuntime.enter(); while (true) { try { if (protocol == null) { LockSupport.parkNanos(500_000_000L); // sleep 500ms continue; } Command cmd = protocol.read(); switch (cmd.getType()) { case Command.EXIT: { log.debug("received exit command"); onCommand(cmd); return; } case Command.DISCONNECT: { log.debug("received disconnect command"); onCommand(cmd); break; } case Command.LIST_PROBES: { onCommand(cmd); break; } case Command.LIST_FAILED_EXTENSIONS: { onCommand(cmd); break; } case Command.EVENT: { BTraceRuntime.Impl rt = getRuntime(); if (rt != null) { rt.handleEvent((EventCommand) cmd); } break; } default: if (log.isDebugEnabled()) { log.debug("received {}", cmd); } // ignore any other command } } catch (Exception exp) { // When the client disconnects normally, ObjectInputStream.read may throw // EOFException. Treat it as a clean shutdown and avoid noisy stack traces // that end up in target stderr during debug runs. if (exp instanceof java.io.EOFException || exp instanceof SocketException) { if (log.isDebugEnabled()) { log.debug("client command stream closed: {}", exp.toString()); } } else { log.debug("Error while processing BTrace command", exp); } break; } } } finally { BTraceRuntime.leave(); try { // Ensure streams and socket are closed once the client side closed first. closeAll(); } catch (IOException ignore) { // best effort } } }); cmdHandler.setDaemon(true); log.debug("starting client command handler thread"); cmdHandler.start(); } @SuppressWarnings("RedundantThrows") @Override public void onCommand(Command cmd) throws IOException { WireProtocol output = protocol; if (output == null) { if (!cmd.isUrgent()) { delayedCommands.add(cmd); } return; } if (log.isDebugEnabled()) { log.debug("client {}: got {}", getClassName(), cmd); } boolean isConnected = true; try { synchronized (output) { output.flush(); } } catch (IOException e) { isConnected = false; } delayedCommands.forEach(new DelayedCommandExecutor(isConnected)); if (!dispatchCommand(cmd, isConnected)) { if (!cmd.isUrgent()) { delayedCommands.add(cmd); } } } private boolean dispatchCommand(Command cmd, boolean isConnected) { if (cmd == Command.NULL) { return true; // do not dispatch the NULL command } WireProtocol output = protocol; Socket socket = sock; if (output == null) { return false; } try { switch (cmd.getType()) { case Command.EXIT: if (isConnected) { output.write(cmd); } onExit(((ExitCommand) cmd).getExitCode()); break; case Command.LIST_PROBES: { if (isConnected) { ((ListProbesCommand) cmd).setProbes(listProbes()); output.write(cmd); } break; } case Command.LIST_FAILED_EXTENSIONS: { if (isConnected) { ((ListFailedExtensionsCommand) cmd).setFailedExtensions( ExtensionRegistry.getFailedExtensions()); output.write(cmd); } break; } case Command.DISCONNECT: { ((DisconnectCommand) cmd).setProbeId(id.toString()); synchronized (output) { output.write(cmd); output.flush(); try { // Half-close the output to allow the client to read DISCONNECT reliably if (socket != null && !socket.isClosed()) { socket.shutdownOutput(); } } catch (IOException ioe) { // ignore; best effort } } if (log.isDebugEnabled()) { log.debug("sent DISCONNECT to client and shutdown socket output"); } disconnected = true; break; } default: if (out != null) { if (cmd instanceof PrintableCommand) { ((PrintableCommand) cmd).print(out); break; } } if (isConnected) { output.write(cmd); } } return true; } catch (IOException e) { return false; } } public boolean isDisconnected() { return disconnected; } @Override protected void sendCommand(Command command) { if (getRuntime() != null) { super.sendCommand(command); return; } // Runtime not yet initialized - send directly via protocol WireProtocol output = protocol; if (output != null) { try { synchronized (output) { output.write(command); output.flush(); } } catch (IOException e) { log.warn("Failed to send command {} via protocol", command.getClass().getSimpleName(), e); } } else { log.warn("Cannot send command {}, neither runtime nor protocol available", command.getClass().getSimpleName()); } } @Override protected void closeAll() throws IOException { super.closeAll(); disconnected = true; WireProtocol output = protocol; if (output != null) { output.close(); protocolUpdater.compareAndSet(this, output, null); } Socket socket = sock; if (socket != null) { socket.close(); sockUpdater.compareAndSet(this, socket, null); } } void reconnect(WireProtocol protocol, Socket socket) throws IOException { this.sock = socket; this.protocol = protocol; this.disconnected = false; onCommand(Command.NULL); } } ================================================ FILE: btrace-agent/src/main/java/org/openjdk/btrace/agent/TraceOutputWriter.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.agent; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.openjdk.btrace.core.SharedSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class represents various strategies available for dumping BTrace output to a file. * * @author Jaroslav Bachorik */ abstract class TraceOutputWriter extends Writer { private static final Logger log = LoggerFactory.getLogger(TraceOutputWriter.class); protected TraceOutputWriter() {} /** * Plain file writer - all output will go to one specified file * * @param output The file to put the output to * @return Returns an appropriate {@linkplain TraceOutputWriter} instance or NULL */ public static TraceOutputWriter fileWriter(File output) { TraceOutputWriter instance = null; try { instance = new SimpleFileWriter(output); } catch (IOException e) { // ignore } return instance; } /** * Time based rolling file writer. Defaults to 100 allowed output chunks. * * @param output The file to put the output to * @param settings The shared settings * @return Returns an appropriate {@linkplain TraceOutputWriter} instance or NULL */ public static TraceOutputWriter rollingFileWriter(File output, SharedSettings settings) { TraceOutputWriter instance = null; try { instance = new TimeBasedRollingFileWriter(output, settings); } catch (IOException e) { // ignore } return instance; } private static void ensurePathExists(File f) { if (f == null || f.exists()) return; ensurePathExists(f.getParentFile()); if (!f.getName().endsWith(".btrace")) { // not creating the actual file try { f.createNewFile(); } catch (IOException e) { log.debug("Failed to create directory: {}", f, e); // ignore and continue } } } private static class SimpleFileWriter extends TraceOutputWriter { private static final Logger log = LoggerFactory.getLogger(SimpleFileWriter.class); private final FileWriter delegate; @SuppressWarnings("DefaultCharset") public SimpleFileWriter(File output) throws IOException { try { File parent = output.getParentFile(); if (parent != null) { output.getParentFile().mkdirs(); } delegate = new FileWriter(output); } catch (IOException e) { log.debug("Failed to create file output {}", output.getName(), e); throw e; } } @Override public void close() throws IOException { delegate.close(); } @Override public void flush() throws IOException { delegate.flush(); } @Override public void write(char[] cbuf, int off, int len) throws IOException { delegate.write(cbuf, off, len); } } @SuppressWarnings("DefaultCharset") private abstract static class RollingFileWriter extends TraceOutputWriter { private static final Logger log = LoggerFactory.getLogger(RollingFileWriter.class); protected final SharedSettings settings; private final ReentrantReadWriteLock writerLock = new ReentrantReadWriteLock(); private final String path, baseName; // @GuardedBy writerLock private FileWriter currentFileWriter; private int counter = 1; public RollingFileWriter(File output, SharedSettings settings) throws IOException { try { output.getParentFile().mkdirs(); currentFileWriter = new FileWriter(output); path = output.getParentFile().getAbsolutePath(); baseName = output.getName(); this.settings = settings; } catch (IOException e) { log.debug("Failed to create rolling file output {}", output.getName(), e); throw e; } } @Override public final void close() throws IOException { try { writerLock.readLock().lock(); currentFileWriter.close(); } finally { writerLock.readLock().unlock(); } } @Override public final void flush() throws IOException { try { writerLock.readLock().lock(); currentFileWriter.flush(); } finally { writerLock.readLock().unlock(); } if (needsRoll()) { nextWriter(); } } @Override public final void write(char[] cbuf, int off, int len) throws IOException { try { writerLock.readLock().lock(); currentFileWriter.write(cbuf, off, len); } finally { writerLock.readLock().unlock(); } } private void nextWriter() { try { writerLock.writeLock().lock(); currentFileWriter = getNextWriter(); } catch (IOException e) { log.debug("Failed to roll over", e); } finally { writerLock.writeLock().unlock(); } } private FileWriter getNextWriter() throws IOException { currentFileWriter.close(); File scriptOutputFile_renameFrom = new File(path + File.separator + baseName); File scriptOutputFile_renameTo = new File(path + File.separator + baseName + "." + (counter++)); if (scriptOutputFile_renameTo.exists()) { scriptOutputFile_renameTo.delete(); } scriptOutputFile_renameFrom.renameTo(scriptOutputFile_renameTo); scriptOutputFile_renameFrom = new File(path + File.separator + baseName); if (counter > settings.getFileRollMaxRolls()) { counter = 1; } return new FileWriter(scriptOutputFile_renameFrom); } protected abstract boolean needsRoll(); } private static class TimeBasedRollingFileWriter extends RollingFileWriter { private final TimeUnit unit = TimeUnit.MILLISECONDS; private long lastTimeStamp = System.currentTimeMillis(); public TimeBasedRollingFileWriter(File output, SharedSettings settings) throws IOException { super(output, settings); } @Override protected boolean needsRoll() { long currTime = System.currentTimeMillis(); long myInterval = currTime - lastTimeStamp; if (unit.convert(myInterval, TimeUnit.MILLISECONDS) >= settings.getFileRollMilliseconds()) { lastTimeStamp = currTime; return true; } return false; } } } ================================================ FILE: btrace-agent/src/main/resources/META-INF/MANIFEST.MF ================================================ Premain-Class: org.openjdk.btrace.agent.Main Agent-Class: org.openjdk.btrace.agent.Main Can-Redefine-Classes: true Can-Retransform-Classes: true Boot-Class-Path: btrace-boot.jar ================================================ FILE: btrace-agent/src/test/java/org/openjdk/btrace/agent/MainTest.java ================================================ package org.openjdk.btrace.agent; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.openjdk.btrace.core.ArgsMap; class MainTest { @Test void locateScriptsEmpty() { ArgsMap argsMap = new ArgsMap(); List scripts = Main.locateScripts(argsMap); assertTrue(scripts.isEmpty()); } @Test void locateScriptsSingle() { ArgsMap argsMap = new ArgsMap(new String[] {"script=script1"}); List scripts = Main.locateScripts(argsMap); assertEquals(1, scripts.size()); } @Test void locateScriptsMulti() { ArgsMap argsMap = new ArgsMap(new String[] {"script=script1:script2"}); List scripts = Main.locateScripts(argsMap); assertEquals(2, scripts.size()); } @Test void locateScriptsDir() throws Exception { Path dir = Files.createTempDirectory("test-"); Path script = Files.createTempFile(dir, "script-", ".btrace"); ArgsMap argsMap = new ArgsMap(new String[] {"scriptdir=" + dir.toString()}); List scripts = Main.locateScripts(argsMap); assertEquals(1, scripts.size()); } } ================================================ FILE: btrace-api/build.gradle ================================================ // BTrace API module for compile-time dependencies // This module provides the compile-time API for BTrace scripts dependencies { implementation project(':btrace-core') implementation project(':btrace-compiler') } ================================================ FILE: btrace-boot/build.gradle ================================================ plugins { id 'java-library' } java { toolchain { languageVersion.set(JavaLanguageVersion.of(8)) } } jar { manifest { attributes( 'Premain-Class': 'org.openjdk.btrace.boot.Loader', 'Agent-Class': 'org.openjdk.btrace.boot.Loader', 'Main-Class': 'org.openjdk.btrace.boot.Loader', 'Can-Redefine-Classes': true, 'Can-Retransform-Classes': true ) } } ================================================ FILE: btrace-boot/src/main/java/org/openjdk/btrace/boot/Loader.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.boot; import java.io.File; import java.io.IOException; import java.lang.instrument.Instrumentation; import java.lang.reflect.Method; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; /** * Entry point and classloader for the masked JAR structure. *

* This class serves as the single entry point for btrace.jar in all modes: *

    *
  • Agent mode: {@code -javaagent:btrace.jar} calls {@link #premain(String, Instrumentation)}
  • *
  • Dynamic attach: calls {@link #agentmain(String, Instrumentation)}
  • *
  • Client mode: {@code java -jar btrace.jar} calls {@link #main(String[])}
  • *
*

* The JAR structure uses .classdata extension for non-bootstrap classes to hide them * from the bootstrap classloader while keeping everything in a single JAR: *

 * btrace.jar
 * ├── org/openjdk/btrace/boot/Loader.class      (bootstrap: this class)
 * ├── org/openjdk/btrace/core/*.class           (bootstrap: core API)
 * ├── org/openjdk/btrace/runtime/*.class        (bootstrap: runtime)
 * ├── META-INF/btrace/agent/*.classdata         (masked: agent classes)
 * └── META-INF/btrace/client/*.classdata        (masked: client classes)
 * 
* * @author Jaroslav Bachorik */ public final class Loader { private static final String AGENT_SECTION = "agent"; private static final String CLIENT_SECTION = "client"; private static final String AGENT_MAIN_ATTR = "BTrace-Agent-Main"; private static final String CLIENT_MAIN_ATTR = "BTrace-Client-Main"; private static final String DEFAULT_AGENT_MAIN = "org.openjdk.btrace.agent.Main"; private static final String DEFAULT_CLIENT_MAIN = "org.openjdk.btrace.client.Main"; private static final boolean DEBUG = Boolean.getBoolean("btrace.boot.debug"); private Loader() { // Utility class } /** * Agent entry point for load-time instrumentation. * * @param args command line arguments * @param inst instrumentation instance */ public static void premain(String args, Instrumentation inst) { debug("premain called with args: " + args); startAgent(args, inst, "premain"); } /** * Agent entry point for dynamic attach. * * @param args command line arguments * @param inst instrumentation instance */ public static void agentmain(String args, Instrumentation inst) { debug("agentmain called with args: " + args); startAgent(args, inst, "agentmain"); } /** * Client entry point for command-line usage. * * @param args command line arguments */ public static void main(String[] args) { debug("main called"); startClient(args); } private static void startAgent(String args, Instrumentation inst, String methodName) { try { debug("startAgent called with args: " + args); // Get JAR file location (tries CodeSource, then getResource() fallback) File jarFile = getJarFile(); if (jarFile == null) { throw new RuntimeException("Cannot locate btrace.jar"); } debug("Loading agent from: " + jarFile.getAbsolutePath()); // Add btrace.jar to bootstrap classloader search path // This is required so that bootstrap-visible classes (BTraceRuntimeAccess, Auxiliary, etc.) // are visible to probe classes which are defined via bootstrap classloader try { inst.appendToBootstrapClassLoaderSearch(new JarFile(jarFile)); debug("Added btrace.jar to bootstrap classpath"); } catch (IOException e) { debug("WARNING: Failed to add btrace.jar to bootstrap classpath: " + e.getMessage()); } // Read main class from manifest String mainClass = getManifestAttribute(jarFile, AGENT_MAIN_ATTR, DEFAULT_AGENT_MAIN); debug("Agent main class: " + mainClass); // Create classloader for agent section // Use null (bootstrap) as parent to ensure bootstrap classes like BTraceRuntimeAccess // are loaded from bootstrap (via appendToBootstrapClassLoaderSearch), not from // Loader's classloader. This ensures probe classes (loaded by bootstrap) see the // same BTraceRuntimeAccess class that the agent uses. MaskedClassLoader agentLoader = new MaskedClassLoader( jarFile, AGENT_SECTION, null); // Load and invoke agent main class Class agentMain = agentLoader.loadClass(mainClass); debug("Loaded agent main class: " + agentMain.getName()); Method method = agentMain.getMethod(methodName, String.class, Instrumentation.class); method.invoke(null, args, inst); debug("Successfully invoked agent main"); } catch (Exception e) { System.err.println("BTrace agent initialization failed: " + e.getMessage()); e.printStackTrace(); throw new RuntimeException("BTrace agent initialization failed", e); } } private static void startClient(String[] args) { try { File jarFile = getJarFile(); if (jarFile == null) { throw new RuntimeException("Cannot locate btrace.jar"); } debug("Loading client from: " + jarFile); // Set system property with btrace.jar location so client can use it as agent JAR System.setProperty("btrace.jar.path", jarFile.getAbsolutePath()); debug("Set btrace.jar.path property to: " + jarFile.getAbsolutePath()); // Read main class from manifest String mainClass = getManifestAttribute(jarFile, CLIENT_MAIN_ATTR, DEFAULT_CLIENT_MAIN); debug("Client main class: " + mainClass); // Create classloader for client section MaskedClassLoader clientLoader = new MaskedClassLoader( jarFile, CLIENT_SECTION, Loader.class.getClassLoader()); // Load and invoke client main class Class clientMain = clientLoader.loadClass(mainClass); Method method = clientMain.getMethod("main", String[].class); method.invoke(null, (Object) args); } catch (Exception e) { System.err.println("BTrace client initialization failed: " + e.getMessage()); e.printStackTrace(); System.exit(1); } } private static String getManifestAttribute(File jarFile, String name, String defaultValue) { try (JarFile jar = new JarFile(jarFile)) { Manifest manifest = jar.getManifest(); if (manifest != null) { Attributes attrs = manifest.getMainAttributes(); String value = attrs.getValue(name); if (value != null && !value.isEmpty()) { return value; } } } catch (IOException e) { debug("Failed to read manifest: " + e.getMessage()); } return defaultValue; } private static File getJarFile() { try { // Try CodeSource first ProtectionDomain pd = Loader.class.getProtectionDomain(); CodeSource cs = pd.getCodeSource(); if (cs != null) { URL location = cs.getLocation(); if (location != null) { debug("CodeSource location: " + location); if ("file".equals(location.getProtocol())) { return new File(location.toURI()); } debug("Unsupported protocol: " + location.getProtocol()); } } // Fallback: use getResource() to find Loader.class in the JAR debug("CodeSource unavailable, trying getResource()"); URL loaderResource = Loader.class.getResource("Loader.class"); if (loaderResource != null) { debug("Loader resource: " + loaderResource); String path = loaderResource.toString(); if (path.startsWith("jar:file:")) { // Extract JAR path from jar:file:/path/to/btrace.jar!/org/openjdk/btrace/boot/Loader.class path = path.substring("jar:file:".length()); int idx = path.indexOf("!"); if (idx > -1) { path = path.substring(0, idx); return new File(path); } } } debug("Could not locate JAR file"); return null; } catch (Exception e) { debug("Error getting JAR file: " + e.getMessage()); return null; } } private static void debug(String msg) { if (DEBUG) { System.err.println("[BTrace Boot] " + msg); } } } ================================================ FILE: btrace-boot/src/main/java/org/openjdk/btrace/boot/MaskedClassLoader.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.boot; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.Enumeration; import java.util.NoSuchElementException; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * ClassLoader that loads classes from .classdata files in a masked section of a JAR. *

* This classloader reads class bytecode from files stored as .classdata instead of .class, * allowing them to be hidden from the bootstrap classloader while remaining in the same JAR. *

* The masked JAR structure has the following sections: *

    *
  • Bootstrap section: Classes stored as .class files, loaded by bootstrap classloader
  • *
  • Shared section: Classes stored as .classdata files in META-INF/btrace/shared/
  • *
  • Agent section: Classes stored as .classdata files in META-INF/btrace/agent/
  • *
  • Client section: Classes stored as .classdata files in META-INF/btrace/client/
  • *
*

* When loading a class, this classloader first checks the specific section (agent or client), * then falls back to the shared section, and finally delegates to the parent classloader. * * @author Jaroslav Bachorik */ public final class MaskedClassLoader extends URLClassLoader { private static final boolean DEBUG = Boolean.getBoolean("btrace.boot.debug"); private final JarFile jarFile; private final File jarPath; private final String sectionPrefix; /** * Creates a new MaskedClassLoader. * * @param jarPath path to the JAR file * @param section section name ("agent" or "client") * @param parent parent classloader * @throws IOException if the JAR file cannot be opened */ public MaskedClassLoader(File jarPath, String section, ClassLoader parent) throws IOException { // Pass empty URL array to avoid URLClassLoader finding regular .class files from btrace.jar // We only want to load from .classdata files (via findClass), delegating all else to parent super(new URL[0], parent); this.jarPath = jarPath; this.jarFile = new JarFile(jarPath); this.sectionPrefix = "META-INF/btrace/" + section + "/"; debug("Created MaskedClassLoader for section: " + section); } /** * Gets the JAR file this classloader is reading from. * * @return the JAR file */ public JarFile getJarFile() { return jarFile; } /** * Gets the path to the JAR file. * * @return the JAR file path */ public File getJarPath() { return jarPath; } @Override protected Class findClass(String name) throws ClassNotFoundException { debug("findClass: " + name); // Convert class name to path: org.foo.Bar -> META-INF/btrace/agent/org/foo/Bar.classdata String classPath = name.replace('.', '/') + ".classdata"; // Try specific section first (agent or client) String path = sectionPrefix + classPath; JarEntry entry = jarFile.getJarEntry(path); // If not found in specific section, try shared section if (entry == null) { path = "META-INF/btrace/shared/" + classPath; entry = jarFile.getJarEntry(path); if (entry != null) { debug("Class found in shared section: " + name); } } if (entry == null) { debug("Class not found in masked sections: " + name); throw new ClassNotFoundException(name); } try { byte[] bytes = readEntry(entry); debug("Loaded " + bytes.length + " bytes for class: " + name); // Define the class with a CodeSource pointing to the JAR URL jarUrl = jarPath.toURI().toURL(); CodeSource codeSource = new CodeSource(jarUrl, (java.security.cert.Certificate[]) null); ProtectionDomain pd = new ProtectionDomain(codeSource, null, this, null); return defineClass(name, bytes, 0, bytes.length, pd); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } @Override public URL findResource(String name) { debug("findResource: " + name); // First check for the resource in the masked section String maskedPath = sectionPrefix + name; JarEntry entry = jarFile.getJarEntry(maskedPath); if (entry != null) { try { return createJarEntryURL(maskedPath); } catch (MalformedURLException e) { debug("Failed to create URL for: " + maskedPath); } } // Fall back to regular resource lookup entry = jarFile.getJarEntry(name); if (entry != null) { try { return createJarEntryURL(name); } catch (MalformedURLException e) { debug("Failed to create URL for: " + name); } } return null; } @Override public Enumeration findResources(String name) throws IOException { debug("findResources: " + name); final URL resource = findResource(name); return new Enumeration() { private boolean hasMore = (resource != null); @Override public boolean hasMoreElements() { return hasMore; } @Override public URL nextElement() { if (!hasMore) { throw new NoSuchElementException(); } hasMore = false; return resource; } }; } @Override public InputStream getResourceAsStream(String name) { debug("getResourceAsStream: " + name); // First check in the masked section String maskedPath = sectionPrefix + name; JarEntry entry = jarFile.getJarEntry(maskedPath); if (entry != null) { try { return jarFile.getInputStream(entry); } catch (IOException e) { debug("Failed to get stream for: " + maskedPath); } } // Fall back to regular resource lookup entry = jarFile.getJarEntry(name); if (entry != null) { try { return jarFile.getInputStream(entry); } catch (IOException e) { debug("Failed to get stream for: " + name); } } return null; } private byte[] readEntry(JarEntry entry) throws IOException { try (InputStream is = jarFile.getInputStream(entry)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } return baos.toByteArray(); } } private URL createJarEntryURL(String entryPath) throws MalformedURLException { // Create a jar: URL for the entry return new URL("jar:" + jarPath.toURI().toURL() + "!/" + entryPath); } @Override public void close() throws IOException { super.close(); jarFile.close(); } private void debug(String msg) { if (DEBUG) { System.err.println("[BTrace Boot] [MaskedCL:" + sectionPrefix + "] " + msg); } } } ================================================ FILE: btrace-boot/src/main/java/org/openjdk/btrace/boot/MaskedJarUtils.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.boot; import java.io.File; import java.io.IOException; import java.util.jar.JarFile; /** * Utility methods for working with masked JAR files. * * @author Jaroslav Bachorik */ public final class MaskedJarUtils { private MaskedJarUtils() { // Utility class } /** * Finds a masked JAR file in the given classpath. * A masked JAR is identified by containing META-INF/btrace/shared/ directory. * * @param classPath the classpath string (File.pathSeparator separated) * @return the masked JAR file, or null if not found */ public static File findMaskedJarInClasspath(String classPath) { if (classPath == null) { return null; } for (String entry : classPath.split(File.pathSeparator)) { File file = new File(entry); if (isMaskedJar(file)) { return file; } } return null; } /** * Finds a masked JAR file in the given classpath entries. * A masked JAR is identified by containing META-INF/btrace/shared/ directory. * * @param cpEntries the classpath entries array * @return the masked JAR file, or null if not found */ public static File findMaskedJarInClasspath(String[] cpEntries) { if (cpEntries == null) { return null; } for (String entry : cpEntries) { File file = new File(entry); if (isMaskedJar(file)) { return file; } } return null; } /** * Checks if the given file is a masked JAR. * A masked JAR is identified by containing META-INF/btrace/shared/ directory. * * @param file the file to check * @return true if the file is a masked JAR, false otherwise */ public static boolean isMaskedJar(File file) { if (file == null || !file.exists() || !file.isFile() || !file.getName().endsWith(".jar")) { return false; } try (JarFile jar = new JarFile(file)) { // Look for the shared section marker return jar.getEntry("META-INF/btrace/shared/") != null; } catch (IOException e) { return false; } } } ================================================ FILE: btrace-bootstrap/build.gradle ================================================ plugins { id 'java-library' alias(libs.plugins.shadow) } def bootIncludes = { if (it.directory) { return true } if (it.path.endsWith('.jar')) { return true } if (it.path.startsWith('org/openjdk/btrace/core/')) { if (it.path == 'org/openjdk/btrace/core/Messages.class' || it.path == 'org/openjdk/btrace/core/messages.properties') { return false } if (it.path.startsWith('org/openjdk/btrace/core/extensions/')) { return false } // Include handlers - probe classes need them in their static initializers if (it.path.startsWith('org/openjdk/btrace/core/handlers/')) { return true } if (it.path.startsWith('org/openjdk/btrace/core/comm/')) { return false } // Include annotations - needed by compiler at compile-time if (it.path.startsWith('org/openjdk/btrace/core/annotations/')) { return true } return true } if (it.path.startsWith('META-INF/services')) { return !it.path.contains('com.sun.') && !it.path.contains('javax.annotation.') } // Include BTraceRuntimeAccess and LinkingFlag from btrace-core (bootstrap classes) if (it.path.startsWith('org/openjdk/btrace/runtime/')) { // Match BTraceRuntimeAccess but NOT BTraceRuntimeAccessImpl if (it.path.startsWith('org/openjdk/btrace/runtime/BTraceRuntimeAccess.class') || it.path.startsWith('org/openjdk/btrace/runtime/BTraceRuntimeAccess$') || it.path.startsWith('org/openjdk/btrace/runtime/LinkingFlag')) { return true } // Include invokedynamic bootstrap classes (they use volatile fields to reach agent implementations) if (it.path.startsWith('org/openjdk/btrace/runtime/Indy') || it.path.startsWith('org/openjdk/btrace/runtime/ExtensionIndy')) { return true } // Include Auxiliary class - probe classes are defined via MethodHandles.privateLookupIn(Auxiliary.class) // and must be bootstrap-visible so they're accessible from application classloaders if (it.path.startsWith('org/openjdk/btrace/runtime/auxiliary/Auxiliary')) { return true } return false } // Include ExtensionBridge interface (referenced by ExtensionIndy) if (it.path == 'org/openjdk/btrace/extension/ExtensionBridge.class') { return true } // Include slf4j for bootstrap classes (DebugSupport needs it) if (it.path.startsWith('org/slf4j/')) { return true } return false } dependencies { implementation project(':btrace-core') implementation project(':btrace-runtime') implementation project(':btrace-instr') implementation project(':btrace-extension') } jar { enabled = false } shadowJar { archiveBaseName.set('btrace-bootstrap') archiveVersion.set('') archiveClassifier.set('') include bootIncludes configurations = [project.configurations.runtimeClasspath] relocate 'org.jctools', 'org.openjdk.btrace.libs.boot.org.jctools' relocate 'org.objectweb.asm', 'org.openjdk.btrace.libs.org.objectweb.asm' relocate 'org.slf4j', 'org.openjdk.btrace.libs.org.slf4j' } assemble.dependsOn shadowJar ================================================ FILE: btrace-client/build.gradle ================================================ import java.text.SimpleDateFormat import java.util.Date buildscript { scriptHandler -> apply from: rootProject.file('buildSrc/shared.gradle'), to: scriptHandler } dependencies { implementation libs.slf4j implementation libs.asm implementation libs.asm.tree implementation libs.asm.util def toolsJar = getToolsJar(); if (toolsJar.getAsFile().exists()) { runtimeOnly files("${toolsJar}") } implementation project(':btrace-core') implementation project(':btrace-compiler') implementation project(':btrace-instr') implementation project(':btrace-boot') } jar { manifest { attributes( 'Built-By' : System.properties['user.name'], 'Build-Timestamp': new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()), 'Build-Revision' : getGitCommit(), 'Created-By' : "Gradle ${gradle.gradleVersion}", 'Build-Jdk' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})", 'Build-OS' : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}", 'Main-Class' : "org.openjdk.btrace.client.Main" ) } } ================================================ FILE: btrace-client/src/main/java/org/openjdk/btrace/client/Client.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.client; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.VirtualMachine; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.ConnectException; import java.net.Socket; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.net.UnknownHostException; import java.nio.file.Files; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openjdk.btrace.boot.MaskedClassLoader; import org.openjdk.btrace.boot.MaskedJarUtils; import org.openjdk.btrace.compiler.Compiler; import org.openjdk.btrace.core.Args; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.core.annotations.DTrace; import org.openjdk.btrace.core.annotations.DTraceRef; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.CommandListener; import org.openjdk.btrace.core.comm.DisconnectCommand; import org.openjdk.btrace.core.comm.EventCommand; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.BinaryWireProtocol; import org.openjdk.btrace.core.comm.JavaSerializationProtocol; import org.openjdk.btrace.core.comm.ListFailedExtensionsCommand; import org.openjdk.btrace.core.comm.ListProbesCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.openjdk.btrace.core.comm.ReconnectCommand; import org.openjdk.btrace.core.comm.SetSettingsCommand; import org.openjdk.btrace.core.comm.StatusCommand; import org.openjdk.btrace.core.comm.ProtocolConfig; import org.openjdk.btrace.core.comm.ProtocolNegotiator; import org.openjdk.btrace.core.comm.ProtocolVersion; import org.openjdk.btrace.core.comm.WireProtocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class represents a BTrace client. This can be used to create command line as well as a GUI * based BTrace clients. The BTrace compilation, traced JVM attach, submission of compiled program * and and I/O to the traced JVM are handled by this class. * * @author A. Sundararajan */ public class Client { private static final Logger log = LoggerFactory.getLogger(Client.class); private static final String DTRACE_DESC; private static final String DTRACE_REF_DESC; private static boolean dtraceEnabled; private static Method submitFile; private static Method submitString; static { try { /* * Check for DTrace Consumer class -- if we don't have that * either /usr/lib/share/java/dtrace.jar is not in CLASSPATH * or we are not running on Solaris 11+. */ Class dtraceConsumerClass = Class.forName("org.opensolaris.os.dtrace.Consumer"); /* * Check for BTrace's DTrace support class -- if that is available * may be the user didn't build BTrace on Solaris 11. S/he built * it on Solaris 10 or below or some other OS. */ Class dtraceClass = Class.forName("com.sun.btrace.dtrace.DTrace"); dtraceEnabled = true; submitFile = dtraceClass.getMethod("submit", File.class, String[].class, CommandListener.class); submitString = dtraceClass.getMethod("submit", String.class, String[].class, CommandListener.class); } catch (Exception exp) { dtraceEnabled = false; } DTRACE_DESC = Type.getDescriptor(DTrace.class); DTRACE_REF_DESC = Type.getDescriptor(DTraceRef.class); } // port on which BTrace agent listens private final int port; // the output file or null private final String outputFile; // are we running debug mode? private final boolean debug; // do we need to track retransforming single classes? (will impose additional overhead) private final boolean trackRetransforms; // are we running in trusted mode? private final boolean trusted; // are we dumping .class files of // the instrumented classes? private final boolean dumpClasses; // which directory we dump the .class files? private final String dumpDir; private final String probeDescPath; private final String statsdDef; // override paths for agent and boot JARs (for jbang support) private final String agentJarOverride; private final String bootJarOverride; // connection state to the traced JVM private volatile Socket sock; private volatile WireProtocol protocol; private boolean disconnected = false; public Client(int port) { this(port, null, ".", false, false, false, false, null, null, null, null); } public Client(int port, String probeDescPath) { this(port, null, probeDescPath, false, false, false, false, null, null, null, null); } public Client( int port, String outputFile, String probeDescPath, boolean debug, boolean trackRetransforms, boolean trusted, boolean dumpClasses, String dumpDir, String statsdDef) { this( port, outputFile, probeDescPath, debug, trackRetransforms, trusted, dumpClasses, dumpDir, statsdDef, null, null); } public Client( int port, String outputFile, String probeDescPath, boolean debug, boolean trackRetransforms, boolean trusted, boolean dumpClasses, String dumpDir, String statsdDef, String agentJarOverride, String bootJarOverride) { this.port = port; this.outputFile = outputFile; this.probeDescPath = probeDescPath; this.debug = debug; this.trusted = trusted; this.dumpClasses = dumpClasses; this.dumpDir = dumpDir; this.trackRetransforms = trackRetransforms; this.statsdDef = statsdDef; this.agentJarOverride = agentJarOverride; this.bootJarOverride = bootJarOverride; } private static boolean isPortAvailable(int port) { Socket clSocket = null; try { clSocket = new Socket("127.0.0.1", port); } catch (IOException ignored) { // ignore } if (clSocket != null) { try { clSocket.close(); } catch (IOException ignored) { // ignore } return false; } return true; } private boolean isAgentAvailableAfterLoadFailure(VirtualMachine vm) { try { Properties serverVmProps = vm.getSystemProperties(); String serverPort = serverVmProps.getProperty("btrace.port"); if (serverPort != null) { return Integer.parseInt(serverPort) == port; } } catch (Exception ignore) { // fall through to port probe } return !isPortAvailable(port); } @SuppressWarnings("DefaultCharset") public byte[] compile(String fileName, String classPath) { return compile(fileName, classPath, new PrintWriter(System.err), null); } /** Compiles given BTrace program using given classpath. */ @SuppressWarnings("DefaultCharset") public byte[] compile(String fileName, String classPath, String includePath) { return compile(fileName, classPath, new PrintWriter(System.err), includePath); } public byte[] compile(String fileName, String classPath, PrintWriter err) { return compile(fileName, classPath, err, null); } /** * Scans the extensions directory and returns a classpath string with all API JARs. * * Looks in the following locations (first match wins): * - System property 'btrace.libs' (assumed to point to BTrace libs directory); scans sibling 'extensions/' * - Derived from java.class.path entry containing 'btrace-client' (for dist layouts); scans sibling 'extensions/' */ private String getExtensionApiClasspath() { try { // 1) Use -Dbtrace.libs if provided String libsProp = System.getProperty("btrace.libs"); if (libsProp != null && !libsProp.isEmpty()) { File libsDir = new File(libsProp); File btraceHome = libsDir.getParentFile(); String cp = scanExtensionsDir(new File(btraceHome, "extensions")); if (!cp.isEmpty()) { return cp; } } // 2) Fall back to locating btrace-client in the classpath String classPath = System.getProperty("java.class.path"); for (String entry : classPath.split(File.pathSeparator)) { if (entry.contains("btrace-client")) { File clientPath = new File(entry); File libsDir = clientPath.getParentFile(); if (libsDir != null) { File btraceHome = libsDir.getParentFile(); String cp = scanExtensionsDir(new File(btraceHome, "extensions")); if (!cp.isEmpty()) { return cp; } } } } } catch (Exception e) { log.warn("Failed to scan extensions directory", e); } return ""; } /** * Builds a classpath string of all *-api.jar files in immediate subdirectories of the given * extensions directory. */ private static String scanExtensionsDir(File extensionsDir) { if (extensionsDir == null || !extensionsDir.exists() || !extensionsDir.isDirectory()) { return ""; } StringBuilder cp = new StringBuilder(); File[] extensionDirs = extensionsDir.listFiles(File::isDirectory); if (extensionDirs != null) { for (File extDir : extensionDirs) { File[] apiJars = extDir.listFiles((dir, name) -> name.endsWith("-api.jar")); if (apiJars != null) { for (File jar : apiJars) { if (cp.length() > 0) { cp.append(File.pathSeparator); } cp.append(jar.getAbsolutePath()); } } } } return cp.toString(); } private WireProtocol createProtocol(Socket socket, String host, boolean allowFallback) throws IOException { ProtocolConfig config = ProtocolConfig.fromSystemProperties(); ProtocolVersion preferred = config.getVersion(); if (config.isAutoNegotiate() && preferred == ProtocolVersion.V2) { try { return createV2Protocol(socket); } catch (IOException e) { if (!allowFallback) { throw e; } closeSocketQuietly(socket); Socket fallback = new Socket(host, port); this.sock = fallback; return createV1Protocol(fallback); } } if (config.isForceVersion() && preferred == ProtocolVersion.V2) { return createV2Protocol(socket); } return createV1Protocol(socket); } private WireProtocol createV1Protocol(Socket socket) throws IOException { InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); return new JavaSerializationProtocol(in, out); } private WireProtocol createV2Protocol(Socket socket) throws IOException { InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); ProtocolNegotiator negotiator = new ProtocolNegotiator(ProtocolVersion.V2); int timeoutMs = ProtocolNegotiator.getNegotiationTimeoutMs(); int previousTimeout = socket.getSoTimeout(); try { socket.setSoTimeout(timeoutMs); ProtocolVersion negotiated = negotiator.negotiateClient(in, out, ProtocolVersion.V2); if (negotiated != ProtocolVersion.V2) { throw new IOException("Protocol negotiation failed: expected V2"); } return new BinaryWireProtocol(in, out); } finally { socket.setSoTimeout(previousTimeout); } } private void closeSocketQuietly(Socket socket) { if (socket == null) { return; } try { socket.close(); } catch (IOException ignore) { } } /** * Compiles given BTrace program using given classpath. Errors and warning are written to given * PrintWriter. */ public byte[] compile(String fileName, String classPath, PrintWriter err, String includePath) { File file = new File(fileName); if (fileName.endsWith(".java")) { Compiler compiler = new Compiler(includePath); classPath += File.pathSeparator + System.getProperty("java.class.path"); // Add extension API JARs to classpath String extApiCp = getExtensionApiClasspath(); if (!extApiCp.isEmpty()) { classPath += File.pathSeparator + extApiCp; } if (log.isDebugEnabled()) { log.debug("compiling {}", fileName); if (!extApiCp.isEmpty()) { log.debug("extension API classpath: {}", extApiCp); } log.debug("compiler classpath: {}", classPath); } // Ensure the verifier's classloader can see the same classpath (TCCL lookup) ClassLoader prevCl = Thread.currentThread().getContextClassLoader(); String prevSysCp = System.getProperty("java.class.path", ""); Map classes; try { String[] cpEntries = classPath.split(File.pathSeparator); // Check if any entry is a masked JAR (contains META-INF/btrace/shared/) File maskedJar = MaskedJarUtils.findMaskedJarInClasspath(cpEntries); ClassLoader compileCl; if (maskedJar != null) { // Use MaskedClassLoader to load classes from .classdata files log.debug("Using MaskedClassLoader for compilation: {}", maskedJar.getAbsolutePath()); compileCl = new MaskedClassLoader(maskedJar, "client", prevCl); } else { // Fall back to URLClassLoader for standard JARs java.net.URL[] urls = new java.net.URL[cpEntries.length]; for (int i = 0; i < cpEntries.length; i++) { urls[i] = new File(cpEntries[i]).toURI().toURL(); } compileCl = new java.net.URLClassLoader(urls, prevCl); } Thread.currentThread().setContextClassLoader(compileCl); // Ensure VerifierVisitor fallback scan (java.class.path) can see extension API jars if (!extApiCp.isEmpty()) { System.setProperty( "java.class.path", prevSysCp.isEmpty() ? extApiCp : (prevSysCp + File.pathSeparator + extApiCp)); } classes = compiler.compile(file, err, ".", classPath); } catch (Exception e) { log.error("Failed to set up compiler classloader", e); classes = compiler.compile(file, err, ".", classPath); } finally { Thread.currentThread().setContextClassLoader(prevCl); System.setProperty("java.class.path", prevSysCp); } if (classes == null) { log.error("btrace compilation for script {} failed!", fileName); return null; } int size = classes.size(); if (size != 1) { log.error("no classes or more than one class in script {}", fileName); return null; } String name = classes.keySet().iterator().next(); byte[] code = classes.get(name); if (log.isDebugEnabled()) { log.debug("compiled {}", fileName); } return code; } else if (fileName.endsWith(".class")) { int codeLen = (int) file.length(); byte[] code = new byte[codeLen]; try { if (log.isDebugEnabled()) { log.debug("reading {}", fileName); } try (FileInputStream fis = new FileInputStream(file)) { int off = 0; int len = 0; do { len = fis.read(code, off, codeLen - off); if (len > -1) { off += len; } } while (off < codeLen && len != -1); } if (log.isDebugEnabled()) { log.debug("read {}", fileName); } return code; } catch (IOException exp) { err.println(exp.getMessage()); return null; } } else { err.println("BTrace script has to be a .java or a .class"); return null; } } public byte[] compileSource(String fileName, String source, String classPath) { return compileSource(fileName, source, classPath, new PrintWriter(System.err), null); } public byte[] compileSource( String fileName, String source, String classPath, PrintWriter err, String includePath) { return compileInternal( fileName, classPath, err, includePath, (compiler, resolvedCp) -> compiler.compile(fileName, source, err, ".", resolvedCp)); } @FunctionalInterface private interface CompilerInvoker { Map compile(Compiler compiler, String classPath) throws Exception; } private byte[] compileInternal( String scriptName, String classPath, PrintWriter err, String includePath, CompilerInvoker invoker) { Compiler compiler = new Compiler(includePath); classPath += File.pathSeparator + System.getProperty("java.class.path"); // Add extension API JARs to classpath String extApiCp = getExtensionApiClasspath(); if (!extApiCp.isEmpty()) { classPath += File.pathSeparator + extApiCp; } if (log.isDebugEnabled()) { log.debug("compiling {}", scriptName); if (!extApiCp.isEmpty()) { log.debug("extension API classpath: {}", extApiCp); } log.debug("compiler classpath: {}", classPath); } // Ensure the verifier's classloader can see the same classpath (TCCL lookup) ClassLoader prevCl = Thread.currentThread().getContextClassLoader(); String prevSysCp = System.getProperty("java.class.path", ""); Map classes; try { String[] cpEntries = classPath.split(File.pathSeparator); URL[] urls = new URL[cpEntries.length]; for (int i = 0; i < cpEntries.length; i++) { urls[i] = new File(cpEntries[i]).toURI().toURL(); } URLClassLoader compileCl = new URLClassLoader(urls, prevCl); Thread.currentThread().setContextClassLoader(compileCl); // Ensure VerifierVisitor fallback scan (java.class.path) can see extension API jars if (!extApiCp.isEmpty()) { System.setProperty( "java.class.path", prevSysCp.isEmpty() ? extApiCp : (prevSysCp + File.pathSeparator + extApiCp)); } classes = invoker.compile(compiler, classPath); } catch (Exception e) { log.error("Failed to set up compiler classloader", e); try { classes = invoker.compile(compiler, classPath); } catch (Exception ex) { log.error("btrace compilation for script {} failed!", scriptName, ex); return null; } } finally { Thread.currentThread().setContextClassLoader(prevCl); System.setProperty("java.class.path", prevSysCp); } if (classes == null) { log.error("btrace compilation for script {} failed!", scriptName); return null; } int size = classes.size(); if (size != 1) { log.error("no classes or more than one class in script {}", scriptName); return null; } String name = classes.keySet().iterator().next(); byte[] code = classes.get(name); if (log.isDebugEnabled()) { log.debug("compiled {}", scriptName); } return code; } /** * Attach the BTrace client to the given Java process. Loads BTrace agent on the target process if * not loaded already. */ public void attach(String pid, String sysCp, String bootCp) throws IOException { try { String agentPath; boolean isMaskedJar = false; // If --agent-jar was specified, use it if (agentJarOverride != null && !agentJarOverride.isEmpty()) { File agentFile = new File(agentJarOverride); if (!agentFile.exists()) { throw new IOException("Specified agent JAR does not exist: " + agentJarOverride); } agentPath = agentFile.getAbsolutePath(); isMaskedJar = isMaskedJar(agentFile); } else { // Find masked btrace.jar in classpath agentPath = findMaskedAgentJar(); if (agentPath == null) { throw new IOException("No masked btrace.jar found in classpath. " + "Please ensure btrace.jar (not btrace-client.jar) is in the classpath."); } // Verify it exists and is readable File agentFile = new File(agentPath); if (!agentFile.exists() || !agentFile.canRead()) { throw new IOException("Agent JAR not found or not readable: " + agentPath); } agentPath = agentFile.getAbsolutePath(); // Normalize to absolute path isMaskedJar = true; // findMaskedAgentJar only returns masked JARs } // Handle boot classpath for masked JAR String effectiveBootCp = bootCp; // For masked JAR, use the agent JAR itself as boot classpath (it has bootstrap classes as .class files) if (isMaskedJar) { if (bootCp == null || bootCp.isEmpty() || bootCp.equals(".")) { effectiveBootCp = agentPath; } else { // If bootCp is set to something else, prepend the masked JAR to it effectiveBootCp = agentPath + File.pathSeparator + bootCp; } } attach(pid, agentPath, sysCp, effectiveBootCp); } catch (RuntimeException | IOException e) { throw e; } catch (Exception exp) { throw new IOException("Failed to attach to PID " + pid, exp); } } /** * Attach the BTrace client to the given Java process. Loads BTrace agent on the target process if * not loaded already. Accepts the full path of the btrace agent jar. Also, accepts system * classpath and boot classpath optionally. */ public void attach(String pid, String agentPath, String sysCp, String bootCp) throws IOException { try { VirtualMachine vm = null; if (log.isDebugEnabled()) { log.debug("attaching to {}", pid); } vm = VirtualMachine.attach(pid); if (log.isDebugEnabled()) { log.debug("checking port availability: {}", port); } Properties serverVmProps = vm.getSystemProperties(); int serverPort = Integer.parseInt(serverVmProps.getProperty("btrace.port", "-1")); if (serverPort != -1) { if (serverPort != port) { throw new IOException( "Can not attach to PID " + pid + " on port " + port + ". There is already a BTrace server active on port " + serverPort + "!"); } } else { if (!isPortAvailable(port)) { throw new IOException("Port " + port + " unavailable."); } } if (log.isDebugEnabled()) { log.debug("attached to {}", pid); log.debug("loading {}", agentPath); } String agentArgs = Args.PORT + "=" + port; if (statsdDef != null) { agentArgs += "," + Args.STATSD + "=" + statsdDef; } if (debug) { agentArgs += "," + Args.DEBUG + "=true"; } if (trusted) { agentArgs += "," + Args.TRUSTED + "=true"; } if (dumpClasses) { agentArgs += "," + Args.DUMP_CLASSES + "=true"; agentArgs += "," + Args.DUMP_DIR + "=" + dumpDir; } if (trackRetransforms) { agentArgs += "," + Args.TRACK_RETRANSFORMS + "=true"; } if (bootCp != null) { agentArgs += "," + Args.BOOT_CLASS_PATH + "=" + bootCp; } String toolsPath = getToolsJarPath( serverVmProps.getProperty("java.class.path"), serverVmProps.getProperty("java.home")); if (sysCp == null) { sysCp = toolsPath; } else { sysCp = sysCp + File.pathSeparator + toolsPath; } agentArgs += "," + Args.SYSTEM_CLASS_PATH + "=" + sysCp; String cmdQueueLimit = System.getProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, null); if (cmdQueueLimit != null) { agentArgs += ",=" + Args.CMD_QUEUE_LIMIT + cmdQueueLimit; } agentArgs += "," + Args.PROBE_DESC_PATH + "=" + probeDescPath; if (log.isDebugEnabled()) { log.debug("agent args: {}", agentArgs); } boolean isJava8 = serverVmProps.getProperty("java.version", "").startsWith("1.8"); // HotSpot on JDK 8 can report AgentLoadException("0") even when the agent loads successfully. // Use a small retry window and treat error code 0 as success only if the agent is reachable. int attempts = isJava8 ? 3 : 1; AgentLoadException lastAle = null; for (int i = 0; i < attempts; i++) { try { vm.loadAgent(agentPath, agentArgs); lastAle = null; break; } catch (AgentLoadException ale) { lastAle = ale; if (isJava8 && ale.getMessage() != null && ale.getMessage().endsWith("0")) { if (isAgentAvailableAfterLoadFailure(vm)) { if (log.isDebugEnabled()) { log.debug("Agent load reported error 0 but agent is available on port {}", port); } lastAle = null; break; } try { Thread.sleep(100); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; } continue; } throw ale; } } if (lastAle != null) { throw lastAle; } if (log.isDebugEnabled()) { log.debug("loaded {}", agentPath); } } catch (RuntimeException | IOException re) { System.err.println("[DEBUG] IOException/RuntimeException during attach:"); re.printStackTrace(); throw re; } catch (Exception exp) { System.err.println("[DEBUG] Exception during attach:"); exp.printStackTrace(); throw new IOException("Failed to attach to PID " + pid, exp); } } void connectAndListProbes(String host, CommandListener listener) throws IOException { if (sock != null) { throw new IllegalStateException(); } try { if (log.isDebugEnabled()) { log.debug("opening socket to {}", port); } long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); while (sock == null && System.nanoTime() <= timeout) { try { sock = new Socket(host, port); } catch (ConnectException e) { log.debug("server not yet available; retrying ..."); Thread.sleep(20); } } if (sock == null) { log.debug("server not available. exiting."); System.exit(1); } protocol = createProtocol(sock, host, true); protocol.write(new ListProbesCommand()); log.debug("entering into command loop"); commandLoop( cmd -> { if (cmd.getType() == Command.LIST_PROBES) { listener.onCommand(cmd); System.exit(0); } else { listener.onCommand(cmd); } }); } catch (UnknownHostException uhe) { throw new IOException(uhe); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } void connectAndListFailedExtensions(String host, CommandListener listener) throws IOException { if (sock != null) { throw new IllegalStateException(); } try { if (log.isDebugEnabled()) { log.debug("opening socket to {}", port); } long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); while (sock == null && System.nanoTime() <= timeout) { try { sock = new Socket(host, port); } catch (ConnectException e) { log.debug("server not yet available; retrying ..."); Thread.sleep(20); } } if (sock == null) { log.debug("server not available. exiting."); System.exit(1); } protocol = createProtocol(sock, host, true); protocol.write(new ListFailedExtensionsCommand()); log.debug("entering into command loop"); commandLoop( cmd -> { if (cmd.getType() == Command.LIST_FAILED_EXTENSIONS) { listener.onCommand(cmd); System.exit(0); } else { listener.onCommand(cmd); } }); } catch (UnknownHostException uhe) { throw new IOException(uhe); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } void reconnect(String host, String resumeProbe, CommandListener listener, String[] command) throws IOException { if (sock != null) { throw new IllegalStateException(); } try { if (log.isDebugEnabled()) { log.debug("opening socket to {}", port); } long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); while (sock == null && System.nanoTime() <= timeout) { try { sock = new Socket(host, port); } catch (ConnectException e) { log.debug("server not yet available; retrying ..."); Thread.sleep(20); } } if (sock == null) { log.debug("server not available. exiting."); System.exit(1); } protocol = createProtocol(sock, host, true); log.debug("reconnecting client {}", resumeProbe); protocol.write(new ReconnectCommand(resumeProbe)); log.debug("entering into command loop"); commandLoop( new CommandListener() { boolean statusReported = false; @Override public void onCommand(Command cmd) throws IOException { if (statusReported || cmd.getType() != Command.STATUS) { listener.onCommand(cmd); } else { StatusCommand statusCommand = (StatusCommand) cmd; if (statusCommand.getFlag() == ReconnectCommand.STATUS_FLAG) { if (statusCommand.isSuccess()) { log.info("Reconnected to an active probe: {}", resumeProbe); String probeCommand = command[0]; String probeCommandArg = command[1]; if (probeCommand != null) { if (log.isDebugEnabled()) { log.debug( "Executing remote command '{}'{}", probeCommand, (probeCommandArg != null ? "(" + probeCommandArg + ")" : "")); } switch (probeCommand) { case "exit": { sendExit(); break; } case "event": { if (probeCommandArg == null || probeCommandArg.equals("*")) { sendEvent(); } else { sendEvent(probeCommandArg); } sendDisconnect(); break; } default: { log.warn("Unrecognized BTrace command {}", probeCommand); } } } } else { log.warn("Unable to reconnect to an active probe: {}", resumeProbe); System.exit(1); } } else { listener.onCommand(cmd); } statusReported = true; } } }); } catch (UnknownHostException uhe) { throw new IOException(uhe); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Submits the compiled BTrace .class to the VM attached and passes given command line arguments. * Receives commands from the traced JVM and sends those to the command listener provided. */ public void submit(String fileName, byte[] code, String[] args, CommandListener listener) throws IOException { submit("localhost", fileName, code, args, listener); } /** * Submits the compiled BTrace .class to the VM attached and passes given command line arguments. * Receives commands from the traced JVM and sends those to the command listener provided. */ public void submit( String host, String fileName, byte[] code, String[] args, CommandListener listener) throws IOException { if (sock != null) { throw new IllegalStateException(); } submitDTrace(fileName, code, args, listener); try { if (log.isDebugEnabled()) { log.debug("opening socket to {}", port); } long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); while (sock == null && System.nanoTime() <= timeout) { try { sock = new Socket(host, port); } catch (ConnectException e) { log.debug("server not yet available; retrying ..."); Thread.sleep(20); } } if (sock == null) { log.debug("server not available. exiting."); System.exit(1); } protocol = createProtocol(sock, host, true); log.debug("setting up client settings"); Map settings = new HashMap<>(); settings.put(SharedSettings.DEBUG_KEY, debug); settings.put(SharedSettings.DUMP_DIR_KEY, dumpClasses ? dumpDir : ""); settings.put(SharedSettings.TRACK_RETRANSFORMS_KEY, trackRetransforms); settings.put(SharedSettings.TRUSTED_KEY, trusted); settings.put(SharedSettings.PROBE_DESC_PATH_KEY, probeDescPath); settings.put(SharedSettings.OUTPUT_FILE_KEY, outputFile); protocol.write(new SetSettingsCommand(settings)); if (log.isDebugEnabled()) { log.debug("sending instrument command: {}", Arrays.deepToString(args)); } protocol.write(new InstrumentCommand(code, args)); log.debug("entering into command loop"); commandLoop( new CommandListener() { boolean statusReported = false; @Override public void onCommand(Command cmd) throws IOException { if (statusReported || cmd.getType() != Command.STATUS) { listener.onCommand(cmd); } else { StatusCommand statusCommand = (StatusCommand) cmd; if (statusCommand.getFlag() == StatusCommand.STATUS_FLAG) { if (statusCommand.isSuccess()) { log.info("Successfully started BTrace probe: {}", fileName); } else { log.warn("Failed to start BTrace probe: {}", fileName); System.exit(1); } statusReported = true; // Forward the first STATUS as well so higher-level listeners (e.g. unattended -x) // can react immediately (e.g. initiate disconnect) listener.onCommand(cmd); } else { listener.onCommand(cmd); } } } }); } catch (UnknownHostException uhe) { throw new IOException(uhe); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Submits the compiled BTrace .class to the VM attached and passes given command line arguments. * Receives commands from the traced JVM and sends those to the command listener provided. */ public void submit(byte[] code, String[] args, CommandListener listener) throws IOException { submit(null, code, args, listener); } /** Sends ExitCommand to the traced JVM. */ public void sendExit() throws IOException { sendExit(0); } /** Sends ExitCommand to the traced JVM. */ public void sendExit(int code) throws IOException { send(new ExitCommand(code)); } public void sendDisconnect() throws IOException { if (log.isDebugEnabled()) { log.debug("sending command {}", DisconnectCommand.class.getSimpleName()); } send(new DisconnectCommand()); } /** Sends an EventCommand to the traced JVM. */ public void sendEvent() throws IOException { sendEvent(""); } /** Sends an EventCommand to the traced JVM. */ public void sendEvent(String name) throws IOException { send(new EventCommand(name)); } /** Closes all connection state to the traced JVM. */ public synchronized void close() throws IOException { // Mark as disconnected to prevent shutdown hook from attempting further sends disconnected = true; if (protocol != null) { protocol.close(); } if (sock != null) { sock.close(); } reset(); } boolean isDisconnected() { return disconnected; } void disconnect() throws IOException { disconnected = true; if (log.isDebugEnabled()) { log.debug("sending DISCONNECT request to agent"); } sendDisconnect(); } void listProbes() throws IOException { send(new ListProbesCommand()); } void listFailedExtensions() throws IOException { send(new ListFailedExtensionsCommand()); } /** reset the internal status of the client */ private void reset() { sock = null; protocol = null; } /** * Finds the masked btrace.jar in the classpath. * * @return the path to the masked JAR, or null if not found */ private String findMaskedAgentJar() { try { // 1. Check system property (set by Loader.main()) String maskedJarPath = System.getProperty("btrace.jar.path"); if (maskedJarPath != null && !maskedJarPath.isEmpty()) { File maskedJar = new File(maskedJarPath); if (maskedJar.exists() && maskedJar.isFile() && isMaskedJar(maskedJar)) { if (log.isDebugEnabled()) { log.debug("Using masked JAR from property: {}", maskedJar.getAbsolutePath()); } return maskedJar.getAbsolutePath(); } } // 2. Search classpath for btrace.jar String classpath = System.getProperty("java.class.path", ""); for (String entry : classpath.split(File.pathSeparator)) { if (entry.endsWith("btrace.jar")) { File jarFile = new File(entry); if (jarFile.exists() && isMaskedJar(jarFile)) { if (log.isDebugEnabled()) { log.debug("Found masked btrace.jar in classpath: {}", jarFile.getAbsolutePath()); } return jarFile.getAbsolutePath(); } } } // 3. No masked JAR found return null; } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Failed to find masked agent JAR: {}", e.getMessage()); } return null; } } /** * Check if a JAR file has the masked JAR structure (contains Loader and masked .classdata files) */ private boolean isMaskedJar(File jarFile) { try (JarFile jar = new JarFile(jarFile)) { // Check for Loader class (unmasked entry point) if (jar.getJarEntry("org/openjdk/btrace/boot/Loader.class") == null) { return false; } // Check for masked agent classes if (jar.getJarEntry("META-INF/btrace/agent/") != null) { return true; } // Also check for at least one .classdata file Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().endsWith(".classdata")) { return true; } } return false; } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("Failed to check if JAR is masked: {}", e.getMessage()); } return false; } } // -- Internals only below this point private String getToolsJarPath(String javaClassPath, String javaHome) { // try to get absolute path of tools.jar // first check this application's classpath String[] components = javaClassPath.split(File.pathSeparator); for (String c : components) { if (c.endsWith("tools.jar")) { return new File(c).getAbsolutePath(); } else if (c.endsWith("classes.jar")) { // MacOS specific return new File(c).getAbsolutePath(); } } // we didn't find -- make a guess! If this app is running on a JDK rather // than a JRE there will be a tools.jar in $JDK_HOME/lib directory. if (System.getProperty("os.name").startsWith("Mac")) { int homeIndex = javaHome.indexOf("/Home"); if (homeIndex > -1) { String java_mac_home = javaHome.substring(0, javaHome.indexOf("/Home")); return java_mac_home + "/Home/lib/tools.jar"; } } // tools.jar is not included in JRE return javaHome + (javaHome.contains("/jre") ? "/.." : "") + "/lib/tools.jar"; } private void send(Command cmd) throws IOException { if (protocol == null) { throw new IllegalStateException(); } protocol.write(cmd); } private void commandLoop(CommandListener listener) throws IOException { assert protocol != null : "null protocol?"; AtomicBoolean exited = new AtomicBoolean(false); while (true) { try { Command cmd = protocol.read(); if (log.isDebugEnabled()) { log.debug("received command {}", cmd); } listener.onCommand(cmd); if (cmd.getType() == Command.EXIT) { log.debug("received EXIT cmd"); return; } } catch (IOException e) { if (exited.compareAndSet(false, true)) listener.onCommand(new ExitCommand(-1)); throw e; } catch (ClassNotFoundException e) { if (exited.compareAndSet(false, true)) listener.onCommand(new ExitCommand(-1)); throw new IOException(e); } catch (NullPointerException e) { if (exited.compareAndSet(false, true)) listener.onCommand(new ExitCommand(-1)); throw new IOException("Protocol closed during command processing", e); } } } private void warn(CommandListener listener, String msg) { try { msg = "WARNING: " + msg + "\n"; listener.onCommand(new MessageCommand(msg)); } catch (IOException exp) { if (log.isDebugEnabled()) { log.debug("Failed to send warning messge", exp); } } } private void submitDTrace(String fileName, byte[] code, String[] args, CommandListener listener) { if (fileName == null || code == null) { return; } Object dtraceSrc = getDTraceSource(fileName, code); try { if (dtraceSrc instanceof String) { if (dtraceEnabled) { submitString.invoke(null, dtraceSrc, args, listener); } else { warn(listener, "@DTrace is supported only on Solaris 11+"); } } else if (dtraceSrc instanceof File) { if (dtraceEnabled) { submitFile.invoke(null, dtraceSrc, args, listener); } else { warn(listener, "@DTraceRef is supported only on Solaris 11+"); } } } catch (IllegalAccessException | IllegalArgumentException iace) { iace.printStackTrace(); } catch (InvocationTargetException ite) { throw new RuntimeException(ite.getTargetException()); } } private Object getDTraceSource(String fileName, byte[] code) { if (isPersistedProbe(code)) { return null; } ClassReader reader = new ClassReader(code); Object[] result = new Object[1]; reader.accept( new ClassVisitor(Opcodes.ASM9) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean vis) { if (desc.equals(DTRACE_DESC)) { return new AnnotationVisitor(Opcodes.ASM9) { @Override public void visit(String name, Object value) { if (name.equals("value")) { result[0] = value; } } }; } else if (desc.equals(DTRACE_REF_DESC)) { return new AnnotationVisitor(Opcodes.ASM9) { @Override public void visit(String name, Object value) { if (name.equals("value")) { String tmp = value.toString(); File file = new File(tmp); if (file.isAbsolute()) { result[0] = file; } else { int index = fileName.lastIndexOf(File.separatorChar); String dir; if (index == -1) { dir = "."; } else { dir = fileName.substring(0, index); } result[0] = new File(dir, tmp); } } } }; } else { return super.visitAnnotation(desc, vis); } } }, ClassReader.SKIP_CODE); return result[0]; } private static boolean isPersistedProbe(byte[] code) { return code[0] == (byte) (0xBA & 0xff) && code[1] == (byte) (0xCE & 0xff) && code[2] == (byte) (0XCA & 0xff) && code[3] == (byte) (0XCA & 0xff); } } ================================================ FILE: btrace-client/src/main/java/org/openjdk/btrace/client/JpsUtils.java ================================================ package org.openjdk.btrace.client; import com.sun.tools.attach.VirtualMachine; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.jvmstat.monitor.MonitoredHost; import sun.jvmstat.monitor.MonitoredVm; import sun.jvmstat.monitor.MonitoredVmUtil; import sun.jvmstat.monitor.VmIdentifier; final class JpsUtils { private static final Logger log = LoggerFactory.getLogger(JpsUtils.class); static Integer findVmByName(String name) { try { return Integer.parseInt(name); } catch (NumberFormatException ignored) { Integer pid = null; try { MonitoredHost vmHost = MonitoredHost.getMonitoredHost((String) null); for (Integer vmPid : MonitoredHost.getMonitoredHost("localhost").activeVms()) { VmIdentifier id = new VmIdentifier(vmPid.toString()); MonitoredVm vm = vmHost.getMonitoredVm(id); String mainClass = MonitoredVmUtil.mainClass(vm, false); if (name.equalsIgnoreCase(mainClass)) { pid = vmPid; break; } } } catch (Exception e) { log.warn("Unexpected exception", e); } return pid; } } static Collection listVms() { Collection vms = new ArrayList<>(); try { MonitoredHost vmHost = MonitoredHost.getMonitoredHost((String) null); for (Integer vmPid : MonitoredHost.getMonitoredHost("localhost").activeVms()) { VmIdentifier id = new VmIdentifier(vmPid.toString()); MonitoredVm mvm = vmHost.getMonitoredVm(id); if (MonitoredVmUtil.isAttachable(mvm)) { String mainClass = MonitoredVmUtil.mainClass(mvm, false); vms.add( "(" + (hasBTraceServer(vmPid) ? "+" : "-") + ") " + vmPid + " " + mainClass + " [" + MonitoredVmUtil.commandLine(mvm) + "]"); } } } catch (Exception e) { log.warn("Unexpected exception", e); } return vms; } static boolean hasBTraceServer(int pid) { boolean result = false; VirtualMachine vm = null; try { vm = VirtualMachine.attach(String.valueOf(pid)); result = vm.getSystemProperties().containsKey("btrace.port"); } catch (Throwable ignored) { } finally { if (vm != null) { try { vm.detach(); } catch (IOException ignored) { } } } return result; } } ================================================ FILE: btrace-client/src/main/java/org/openjdk/btrace/client/Main.java ================================================ /* * Copyright (c) 2008-2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.client; import java.io.Console; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Properties; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.OnelinerNode; import org.openjdk.btrace.compiler.oneliner.OnelinerCodeGenerator; import org.openjdk.btrace.compiler.oneliner.OnelinerException; import org.openjdk.btrace.compiler.oneliner.OnelinerParser; import org.openjdk.btrace.compiler.oneliner.OnelinerValidator; import org.openjdk.btrace.core.DebugSupport; import org.openjdk.btrace.core.Messages; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.CommandListener; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.PrintableCommand; import org.openjdk.btrace.core.comm.StatusCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.misc.Signal; /** * This is the main class for a simple command line BTrace client. It is possible to create a GUI * client using the Client class. * * @author A. Sundararajan */ @SuppressWarnings("RedundantThrows") public final class Main { private static final Logger log; public static final boolean TRACK_RETRANSFORM; public static final int BTRACE_DEFAULT_PORT = 2020; public static final String BTRACE_DEFAULT_HOST = "localhost"; private static final Console con; private static final PrintWriter out; public static volatile boolean exiting; private static boolean DEBUG; private static boolean TRUSTED; private static boolean DUMP_CLASSES; private static String OUTPUT_FILE; private static String DUMP_DIR; private static String PROBE_DESC_PATH; private static String AGENT_JAR_OVERRIDE; private static String BOOT_JAR_OVERRIDE; private static String EXTRACT_AGENT_DIR; private static boolean ONELINER_MODE; private static String ONELINER_SCRIPT; static { DebugSupport.initLoggers(Boolean.getBoolean("com.sun.btrace.debug"), null); // initialize the logger instance log = LoggerFactory.getLogger(Main.class); if (isDebug()) { log.debug("btrace debug mode is set"); } TRACK_RETRANSFORM = Boolean.getBoolean("com.sun.btrace.trackRetransforms"); if (TRACK_RETRANSFORM) log.debug("trackRetransforms flag is set"); TRUSTED = Boolean.getBoolean("com.sun.btrace.unsafe"); TRUSTED |= Boolean.getBoolean("com.sun.btrace.trusted"); if (TRUSTED) log.debug("btrace trusted mode is set"); DUMP_CLASSES = Boolean.getBoolean("com.sun.btrace.dumpClasses"); if (DUMP_CLASSES) log.debug("dumpClasses flag is set"); DUMP_DIR = System.getProperty("com.sun.btrace.dumpDir", "."); if (DUMP_CLASSES) { if (log.isDebugEnabled()) log.debug("dumpDir is {}", DUMP_DIR); } PROBE_DESC_PATH = System.getProperty("com.sun.btrace.probeDescPath", "."); String javaVersion = getJavaVersion(); // In Java 22 the console will write standard output to stderr :shrug: con = javaVersion == null || !javaVersion.startsWith("22") ? System.console() : null; out = getOutWriter(con); } private static String getJavaVersion() { Properties props = new Properties(); try { props.load(Files.newInputStream(Paths.get(System.getenv("JAVA_HOME"), "release"))); return props.getProperty("JAVA_VERSION").replace("\"", ""); } catch (IOException ignored) { return null; } } @SuppressWarnings("DefaultCharset") private static PrintWriter getOutWriter(Console con) { return (con != null) ? con.writer() : new PrintWriter(System.out); } public static void main(String[] args) throws Exception { int port = BTRACE_DEFAULT_PORT; String host = BTRACE_DEFAULT_HOST; String classPath = "."; String includePath = null; int count = 0; boolean hostDefined = false; boolean portDefined = false; boolean classpathDefined = false; boolean includePathDefined = false; String statsdDef = ""; String resumeProbe = null; String probeCommand = null; String probeCommandArg = null; boolean listProbes = false; boolean listFailedExtensions = false; boolean unattended = false; for (int i = 0; i < args.length; i++) { String arg = args[i]; switch (arg) { case "-v": DEBUG = true; DebugSupport.initLoggers(true, log); break; case "--version": System.out.println(Messages.get("btrace.version")); return; case "-l": for (String vm : JpsUtils.listVms()) { System.out.println(vm); } return; case "-r": if (i < args.length - 1) { if (args[i + 1].equalsIgnoreCase("help")) { System.out.println(Messages.get("remote.commands.help")); return; } } break; case "--extract-agent": if (i < args.length - 1 && !args[i + 1].startsWith("-")) { EXTRACT_AGENT_DIR = args[i + 1]; } else { EXTRACT_AGENT_DIR = "."; } handleExtractAgent(); return; } } if (args.length < 2) { usage(); } for (; ; ) { if (args[count].isEmpty()) { continue; } if (args[count].charAt(0) == '-') { if (args.length <= count + 1) { usage(); } if (args[count].equals("-p") && !portDefined) { try { port = Integer.parseInt(args[++count]); if (log.isDebugEnabled()) log.debug("accepting port {}", port); } catch (NumberFormatException nfe) { usage(); } portDefined = true; } else if (args[count].equals("-u")) { TRUSTED = true; log.debug("btrace trusted mode is set"); } else if (args[count].equals("-o")) { OUTPUT_FILE = args[++count]; if (log.isDebugEnabled()) log.debug("outputFile is {}", OUTPUT_FILE); } else if (args[count].equals("-d")) { DUMP_CLASSES = true; DUMP_DIR = args[++count]; if (log.isDebugEnabled()) log.debug("dumpDir is {}", DUMP_DIR); } else if (args[count].equals("-pd")) { PROBE_DESC_PATH = args[++count]; if (log.isDebugEnabled()) log.debug("probeDescDir is {}", PROBE_DESC_PATH); } else if ((args[count].equals("-cp") || args[count].equals("-classpath")) && !classpathDefined) { classPath = args[++count]; if (log.isDebugEnabled()) log.debug("accepting classpath {}", classPath); classpathDefined = true; } else if (args[count].equals("-I") && !includePathDefined) { includePath = args[++count]; if (log.isDebugEnabled()) log.debug("accepting include path {}", includePath); includePathDefined = true; } else if (args[count].equals("-statsd")) { statsdDef = args[++count]; } else if (args[count].equals("-v")) { // already processed } else if (args[count].equals("-host") && !hostDefined) { host = args[++count]; hostDefined = true; } else if (args[count].equals("-r")) { if (log.isDebugEnabled()) log.debug("reconnecting to an already active probe {}", resumeProbe); resumeProbe = args[++count]; if (count < args.length - 2 && !args[count + 1].startsWith("-")) { probeCommand = args[++count].toLowerCase(); if (probeCommand.equalsIgnoreCase("event") && count < args.length - 2) { probeCommandArg = args[++count]; } } if (probeCommand != null && log.isDebugEnabled()) { log.debug( "executing probe command '{}'{}", probeCommand, (probeCommandArg != null ? "(" + probeCommandArg + ")" : "")); } } else if (args[count].equals("-lp")) { log.debug("listing active probes"); listProbes = true; } else if (args[count].equals("-le")) { log.debug("listing failed extensions"); listFailedExtensions = true; } else if (args[count].equals("-x")) { log.debug("submitting probe in unattended mode"); unattended = true; } else if (args[count].equals("--agent-jar")) { AGENT_JAR_OVERRIDE = args[++count]; if (log.isDebugEnabled()) log.debug("agent JAR override: {}", AGENT_JAR_OVERRIDE); } else if (args[count].equals("--boot-jar")) { BOOT_JAR_OVERRIDE = args[++count]; if (log.isDebugEnabled()) log.debug("boot JAR override: {}", BOOT_JAR_OVERRIDE); } else if (args[count].equals("-n") || args[count].equals("--oneliner")) { ONELINER_MODE = true; ONELINER_SCRIPT = args[++count]; if (log.isDebugEnabled()) log.debug("oneliner: {}", ONELINER_SCRIPT); } else { usage(); } count++; if (count >= args.length) { break; } } else { break; } } if (!portDefined) { if (log.isDebugEnabled()) log.debug("assuming default port {}", port); } if (!classpathDefined) { if (log.isDebugEnabled()) log.debug("assuming default classpath '{}'", classPath); } if (args.length < (count + 1)) { usage(); } String pidArg = args[count]; Integer pid = JpsUtils.findVmByName(pidArg); if (pid == null) { errorExit("Unable to find JVM with either PID or name: " + pidArg, 1); } else { log.info("Attaching BTrace to PID: {}", pid); } try { Client client = new Client( port, OUTPUT_FILE, PROBE_DESC_PATH, DEBUG, TRACK_RETRANSFORM, TRUSTED, DUMP_CLASSES, DUMP_DIR, statsdDef, AGENT_JAR_OVERRIDE, BOOT_JAR_OVERRIDE); if (resumeProbe != null) { registerExitHook(client); if (con != null) { registerSignalHandler(client); } client.reconnect( host, resumeProbe, createCommandListener(client), new String[] {probeCommand, probeCommandArg}); } else if (listProbes) { registerExitHook(client); client.attach(pid.toString(), null, classPath); client.connectAndListProbes(host, createCommandListener(client)); System.exit(0); } else if (listFailedExtensions) { registerExitHook(client); client.attach(pid.toString(), null, classPath); client.connectAndListFailedExtensions(host, createCommandListener(client)); System.exit(0); } else { String fileName; byte[] code; String[] btraceArgs; if (ONELINER_MODE) { // Oneliner mode: parse and generate Java source from oneliner try { OnelinerNode ast = OnelinerParser.parse(ONELINER_SCRIPT); OnelinerValidator.validate(ast, ONELINER_SCRIPT); String className = "BTraceOneliner_" + System.currentTimeMillis(); String javaSource = OnelinerCodeGenerator.generate(ast, className); fileName = className + ".java"; // Extract script args btraceArgs = new String[args.length - count - 1]; if (btraceArgs.length > 0) { System.arraycopy(args, count + 1, btraceArgs, 0, btraceArgs.length); } if (log.isDebugEnabled()) { log.debug("Generated BTrace source:\n{}", javaSource); } if (Boolean.getBoolean("btrace.oneliner.dump")) { System.err.println("=== Generated oneliner source (" + fileName + ") ==="); System.err.println(javaSource); } code = client.compileSource(fileName, javaSource, classPath, new PrintWriter(System.err), includePath); if (code == null) { errorExit("Oneliner compilation failed", 1); } } catch (OnelinerException e) { errorExit(e.getMessage(), 1); return; } } else { // File mode: compile from file fileName = args[count + 1]; btraceArgs = new String[args.length - count - 2]; if (btraceArgs.length > 0) { System.arraycopy(args, count + 2, btraceArgs, 0, btraceArgs.length); } if (!new File(fileName).exists()) { errorExit("File not found: " + fileName, 1); } code = client.compile(fileName, classPath, includePath); if (code == null) { errorExit("BTrace compilation failed", 1); } } if (log.isDebugEnabled()) { log.debug("Boot classpath: {}", classPath); } if (!hostDefined) client.attach(pid.toString(), null, classPath); registerExitHook(client); if (con != null) { registerSignalHandler(client); } log.debug("submitting the BTrace program"); CommandListener listener = createCommandListener(client); final boolean isUnattended = unattended; client.submit( host, fileName, code, btraceArgs, cmd -> { if (isUnattended && cmd.getType() == Command.STATUS && ((StatusCommand) cmd).getFlag() == StatusCommand.STATUS_FLAG) { // In unattended mode, initiate a graceful disconnect and // continue processing so the server can send DISCONNECT // with the probe id, which the client prints. try { if (log.isDebugEnabled()) { log.debug("initiating unattended disconnect after STATUS OK"); } client.disconnect(); // marks disconnected and sends DISCONNECT } catch (IOException ioe) { if (log.isDebugEnabled()) { log.debug("error initiating unattended disconnect: {}", ioe.toString()); } } // Continue processing commands so DISCONNECT is handled. } else { listener.onCommand(cmd); } }); } } catch (IOException exp) { errorExit(exp.getMessage(), 1); } } private static CommandListener createCommandListener(Client client) { return cmd -> { int type = cmd.getType(); if (cmd instanceof PrintableCommand) { ((PrintableCommand) cmd).print(out); out.flush(); } else if (type == Command.EXIT) { exiting = true; out.flush(); ExitCommand ecmd = (ExitCommand) cmd; System.exit(ecmd.getExitCode()); } if (type == Command.DISCONNECT) { System.exit(0); } }; } private static void registerExitHook(Client client) { log.debug("registering shutdown hook"); Runtime.getRuntime() .addShutdownHook( new Thread( () -> { if (!exiting) { try { if (!client.isDisconnected()) { log.debug("sending exit command"); client.sendExit(0); } else { // Disconnect already initiated; avoid duplicate sends on shutdown log.debug("client already marked disconnected; skipping shutdown send"); } } catch (IOException | IllegalStateException ioexp) { // Streams may already be closed (e.g., unattended mode) if (log.isDebugEnabled()) log.debug(ioexp.toString(), ioexp); } } })); } private static void registerSignalHandler(Client client) { log.debug("registering signal handler for SIGINT"); Signal.handle( new Signal("INT"), sig -> { try { con.printf("Please enter your option:\n"); con.printf( "\t1. exit\n\t2. send an event\n\t3. send a named event\n\t4. flush console output\n\t5. list probes\n\t6. detach client\n\t7. list failed extensions\n"); con.flush(); String option = con.readLine(); if (option == null) { return; } option = option.trim(); switch (option) { case "1": System.exit(0); case "2": log.debug("sending event command"); client.sendEvent(); break; case "3": con.printf("Please enter the event name: "); String name = con.readLine(); if (name != null) { log.debug("sending event command"); client.sendEvent(name); } break; case "4": out.flush(); break; case "5": client.listProbes(); break; case "6": client.disconnect(); break; case "7": client.listFailedExtensions(); break; default: con.printf("invalid option!\n"); break; } } catch (IOException ioexp) { log.debug(ioexp.toString(), ioexp); } }); } private static void usage() { System.err.println(Messages.get("btrace.usage")); System.exit(1); } private static boolean isDebug() { return DEBUG; } private static void errorExit(String msg, int code) { exiting = true; System.err.println(msg); System.exit(code); } /** * Extracts embedded agent JARs from the uber JAR to the specified directory. */ private static void handleExtractAgent() { if (EXTRACT_AGENT_DIR == null) { return; } File outputDir = new File(EXTRACT_AGENT_DIR); if (outputDir.exists() && !outputDir.isDirectory()) { errorExit("Output path is not a directory: " + outputDir.getAbsolutePath(), 1); } if (!outputDir.exists() && !outputDir.mkdirs()) { errorExit("Failed to create output directory: " + outputDir.getAbsolutePath(), 1); } URL btraceLoc = Main.class.getProtectionDomain().getCodeSource().getLocation(); if (btraceLoc == null) { errorExit("Unable to locate BTrace JAR for extraction.", 1); } File btraceFile; try { btraceFile = new File(btraceLoc.toURI()); } catch (Exception e) { errorExit("Invalid BTrace location: " + btraceLoc, 1); return; } if (!btraceFile.isFile()) { errorExit("BTrace location is not a JAR file: " + btraceFile.getAbsolutePath(), 1); } try (JarFile btrace = new JarFile(btraceFile)) { File agentFile = new File(outputDir, "btrace-agent.jar"); File bootFile = new File(outputDir, "btrace-boot.jar"); extractJar(btrace, "META-INF/embedded/btrace-agent.jar", agentFile); extractJar(btrace, "META-INF/embedded/btrace-boot.jar", bootFile); System.out.println("Extracted agent JARs to: " + outputDir.getAbsolutePath()); System.out.println(" - " + agentFile.getName()); System.out.println(" - " + bootFile.getName()); System.exit(0); } catch (Exception e) { errorExit("Failed to extract agent JARs: " + e.getMessage(), 1); } } /** * Extracts a single JAR entry from the source JAR to the target file. */ private static void extractJar(JarFile source, String entryPath, File target) throws IOException { JarEntry entry = source.getJarEntry(entryPath); if (entry == null) { throw new IOException("Embedded JAR not found: " + entryPath); } // Validate that the entry name does not contain path traversal sequences String normalizedEntry = entry.getName(); if (normalizedEntry.contains("..")) { throw new IOException("Zip Slip: entry contains path traversal: " + normalizedEntry); } try (InputStream in = source.getInputStream(entry); OutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } } } ================================================ FILE: btrace-client/src/main/java/org/openjdk/btrace/client/ProbePrinter.java ================================================ package org.openjdk.btrace.client; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.io.PrintWriter; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Set; import org.objectweb.asm.ClassReader; import org.objectweb.asm.util.TraceClassVisitor; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.instr.BTraceProbe; import org.openjdk.btrace.instr.BTraceProbeFactory; import org.openjdk.btrace.instr.OnMethod; import org.openjdk.btrace.instr.OnProbe; public final class ProbePrinter { public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("Usage: btracep "); System.exit(0); } Path probePath = Paths.get(args[0]); try (InputStream probeDataStream = new BufferedInputStream(new FileInputStream(probePath.toFile()))) { BTraceProbe probe = new BTraceProbeFactory(SharedSettings.GLOBAL).createProbe(probeDataStream); probe.checkVerified(); System.out.println("Name: " + probe.getClassName(false)); System.out.println("Verified: " + probe.isVerified()); System.out.println("Transforming: " + probe.isTransforming()); Set requiredPermissions = probe.getRequiredPermissions(); if (!requiredPermissions.isEmpty()) { System.out.println("Required permissions: " + requiredPermissions); } System.out.println("=== Probe handlers"); for (OnMethod om : probe.onmethods()) { System.out.println(om); } for (OnProbe op : probe.onprobes()) { System.out.println(op); } System.out.println("=== Dataholder class"); ClassReader dataholderReader = new ClassReader(probe.getDataHolderBytecode()); dataholderReader.accept(new TraceClassVisitor(new PrintWriter(System.out)), 0); System.out.println("=== Full probe class"); ClassReader probeReader = new ClassReader(probe.getFullBytecode()); probeReader.accept(new TraceClassVisitor(new PrintWriter(System.out)), 0); } } } ================================================ FILE: btrace-client/src/test/java/org/openjdk/btrace/client/ClientTest.java ================================================ package org.openjdk.btrace.client; import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class ClientTest { @TempDir Path tempDir; private File createUberJar() throws IOException { File uberJar = tempDir.resolve("btrace.jar").toFile(); try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(uberJar))) { // Add a dummy embedded agent JAR JarEntry agentEntry = new JarEntry("META-INF/embedded/btrace-agent.jar"); jos.putNextEntry(agentEntry); jos.write("dummy agent content".getBytes()); jos.closeEntry(); // Add a dummy embedded boot JAR JarEntry bootEntry = new JarEntry("META-INF/embedded/btrace-boot.jar"); jos.putNextEntry(bootEntry); jos.write("dummy boot content".getBytes()); jos.closeEntry(); // Add a marker class to make it a valid JAR JarEntry classEntry = new JarEntry("org/openjdk/btrace/client/Client.class"); jos.putNextEntry(classEntry); jos.write(new byte[0]); jos.closeEntry(); } return uberJar; } @Test void testConstructorWithOverrides() { String agentJar = "/path/to/agent.jar"; String bootJar = "/path/to/boot.jar"; Client client = new Client( 2020, null, ".", false, false, false, false, null, null, agentJar, bootJar); // Use reflection to verify private fields (since they're not exposed) try { Field agentField = Client.class.getDeclaredField("agentJarOverride"); agentField.setAccessible(true); assertEquals(agentJar, agentField.get(client)); Field bootField = Client.class.getDeclaredField("bootJarOverride"); bootField.setAccessible(true); assertEquals(bootJar, bootField.get(client)); } catch (Exception e) { fail("Failed to access private fields: " + e.getMessage()); } } @Test void testConstructorWithNullOverrides() { Client client = new Client( 2020, null, ".", false, false, false, false, null, null, null, null); try { Field agentField = Client.class.getDeclaredField("agentJarOverride"); agentField.setAccessible(true); assertNull(agentField.get(client)); Field bootField = Client.class.getDeclaredField("bootJarOverride"); bootField.setAccessible(true); assertNull(bootField.get(client)); } catch (Exception e) { fail("Failed to access private fields: " + e.getMessage()); } } @Test void testExtractEmbeddedAgentJarNotFound() throws Exception { // Create a regular JAR without embedded JARs File regularJar = tempDir.resolve("regular.jar").toFile(); try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(regularJar))) { JarEntry entry = new JarEntry("some/Class.class"); jos.putNextEntry(entry); jos.write(new byte[0]); jos.closeEntry(); } // This test would need to mock the Client.class location // For now, we just verify the JAR exists assertTrue(regularJar.exists()); } @Test void testUberJarCreation() throws Exception { File uberJar = createUberJar(); assertTrue(uberJar.exists()); // Verify embedded JARs exist try (JarFile jar = new JarFile(uberJar)) { assertNotNull(jar.getJarEntry("META-INF/embedded/btrace-agent.jar")); assertNotNull(jar.getJarEntry("META-INF/embedded/btrace-boot.jar")); } } @Test void testAgentJarOverrideTakesPrecedence() { // When agentJarOverride is set, it should be used instead of discovery String overridePath = "/custom/path/btrace-agent.jar"; Client client = new Client(2020, null, ".", false, false, false, false, null, null, overridePath, null); try { Field agentField = Client.class.getDeclaredField("agentJarOverride"); agentField.setAccessible(true); assertEquals(overridePath, agentField.get(client)); } catch (Exception e) { fail("Failed to verify agentJarOverride: " + e.getMessage()); } } @Test void testBootJarOverridePrependsToBootCp() { // Boot JAR override should be prepended to boot classpath String bootOverride = "/custom/boot.jar"; Client client = new Client(2020, null, ".", false, false, false, false, null, null, null, bootOverride); try { Field bootField = Client.class.getDeclaredField("bootJarOverride"); bootField.setAccessible(true); assertEquals(bootOverride, bootField.get(client)); } catch (Exception e) { fail("Failed to verify bootJarOverride: " + e.getMessage()); } } @Test void testBackwardCompatibility() { // Old constructor should still work (no overrides) Client client = new Client(2020, null, ".", false, false, false, false, null, null); try { Field agentField = Client.class.getDeclaredField("agentJarOverride"); agentField.setAccessible(true); assertNull(agentField.get(client)); Field bootField = Client.class.getDeclaredField("bootJarOverride"); bootField.setAccessible(true); assertNull(bootField.get(client)); } catch (Exception e) { fail("Failed to verify backward compatibility: " + e.getMessage()); } } } ================================================ FILE: btrace-client/src/test/java/org/openjdk/btrace/client/MainTest.java ================================================ package org.openjdk.btrace.client; import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class MainTest { @TempDir Path tempDir; /** * Creates a mock uber JAR with embedded agent and boot JARs for testing extraction. */ private File createTestUberJar() throws IOException { File uberJar = tempDir.resolve("btrace-test.jar").toFile(); try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(uberJar))) { // Add embedded agent JAR JarEntry agentEntry = new JarEntry("META-INF/embedded/btrace-agent.jar"); jos.putNextEntry(agentEntry); jos.write("test agent jar content".getBytes()); jos.closeEntry(); // Add embedded boot JAR JarEntry bootEntry = new JarEntry("META-INF/embedded/btrace-boot.jar"); jos.putNextEntry(bootEntry); jos.write("test boot jar content".getBytes()); jos.closeEntry(); } return uberJar; } @Test void testExtractJar() throws Exception { File sourceJar = createTestUberJar(); File targetFile = tempDir.resolve("extracted.jar").toFile(); // Use reflection to access private extractJar method Method extractJarMethod = Main.class.getDeclaredMethod( "extractJar", JarFile.class, String.class, File.class); extractJarMethod.setAccessible(true); try (JarFile jarFile = new JarFile(sourceJar)) { extractJarMethod.invoke(null, jarFile, "META-INF/embedded/btrace-agent.jar", targetFile); } // Verify extraction assertTrue(targetFile.exists()); assertTrue(targetFile.length() > 0); // Verify content byte[] content = Files.readAllBytes(targetFile.toPath()); assertEquals("test agent jar content", new String(content)); } @Test void testExtractJarMissingEntry() throws Exception { File sourceJar = createTestUberJar(); File targetFile = tempDir.resolve("extracted.jar").toFile(); Method extractJarMethod = Main.class.getDeclaredMethod( "extractJar", JarFile.class, String.class, File.class); extractJarMethod.setAccessible(true); try (JarFile jarFile = new JarFile(sourceJar)) { Exception exception = assertThrows( Exception.class, () -> { extractJarMethod.invoke( null, jarFile, "META-INF/embedded/nonexistent.jar", targetFile); }); Throwable cause = exception.getCause(); assertTrue(cause instanceof IOException, "Expected IOException as cause"); assertTrue( cause.getMessage().contains("Embedded JAR not found"), "Expected error message about missing embedded JAR"); } } @Test void testExtractJarToDirectory() throws Exception { File sourceJar = createTestUberJar(); File outputDir = tempDir.resolve("output").toFile(); assertTrue(outputDir.mkdirs()); File agentFile = new File(outputDir, "btrace-agent.jar"); File bootFile = new File(outputDir, "btrace-boot.jar"); Method extractJarMethod = Main.class.getDeclaredMethod( "extractJar", JarFile.class, String.class, File.class); extractJarMethod.setAccessible(true); try (JarFile jarFile = new JarFile(sourceJar)) { extractJarMethod.invoke(null, jarFile, "META-INF/embedded/btrace-agent.jar", agentFile); extractJarMethod.invoke(null, jarFile, "META-INF/embedded/btrace-boot.jar", bootFile); } // Verify both files were extracted assertTrue(agentFile.exists()); assertTrue(bootFile.exists()); assertTrue(agentFile.length() > 0); assertTrue(bootFile.length() > 0); } @Test void testExtractAgentCreatesDirectory() throws Exception { File nonExistentDir = tempDir.resolve("newdir").toFile(); assertFalse(nonExistentDir.exists()); // Test that handleExtractAgent would create the directory // This is tested indirectly by verifying directory creation logic assertTrue(nonExistentDir.mkdirs() || nonExistentDir.exists()); assertTrue(nonExistentDir.exists()); assertTrue(nonExistentDir.isDirectory()); } @Test void testCLIFlagsParsing() throws Exception { // Test that static fields can be set (simulating CLI flag parsing) // This verifies the fields exist and are accessible for setting Field agentJarField = Main.class.getDeclaredField("AGENT_JAR_OVERRIDE"); agentJarField.setAccessible(true); assertNotNull(agentJarField); Field bootJarField = Main.class.getDeclaredField("BOOT_JAR_OVERRIDE"); bootJarField.setAccessible(true); assertNotNull(bootJarField); Field extractDirField = Main.class.getDeclaredField("EXTRACT_AGENT_DIR"); extractDirField.setAccessible(true); assertNotNull(extractDirField); } @Test void testExtractJarWithLargeContent() throws Exception { // Create a JAR with larger content to test buffer handling File sourceJar = tempDir.resolve("large-btrace.jar").toFile(); byte[] largeContent = new byte[16384]; // 16KB for (int i = 0; i < largeContent.length; i++) { largeContent[i] = (byte) (i % 256); } try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(sourceJar))) { JarEntry entry = new JarEntry("META-INF/embedded/btrace-agent.jar"); jos.putNextEntry(entry); jos.write(largeContent); jos.closeEntry(); } File targetFile = tempDir.resolve("extracted-large.jar").toFile(); Method extractJarMethod = Main.class.getDeclaredMethod( "extractJar", JarFile.class, String.class, File.class); extractJarMethod.setAccessible(true); try (JarFile jarFile = new JarFile(sourceJar)) { extractJarMethod.invoke(null, jarFile, "META-INF/embedded/btrace-agent.jar", targetFile); } // Verify complete extraction assertTrue(targetFile.exists()); assertEquals(largeContent.length, targetFile.length()); // Verify content integrity byte[] extractedContent = Files.readAllBytes(targetFile.toPath()); assertArrayEquals(largeContent, extractedContent); } } ================================================ FILE: btrace-compiler/build.gradle ================================================ dependencies { implementation libs.slf4j implementation libs.slf4j.simple implementation libs.asm implementation libs.asm.util def toolsJar = getToolsJar(); if (toolsJar.getAsFile().exists()) { runtimeOnly files("${toolsJar}") } implementation project(path: ':btrace-core') implementation project(path: ':btrace-runtime') implementation project(path: ':btrace-boot') runtimeOnly project(path: ':btrace-instr') testImplementation project(path: ':btrace-instr') testImplementation project(path: ':btrace-extensions:btrace-metrics') } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/AnnotationSerializer.java ================================================ package org.openjdk.btrace.compiler; import org.objectweb.asm.tree.AnnotationNode; public class AnnotationSerializer { public static void serialize(AnnotationNode an, StringBuilder sb) { sb.append("{type:").append(an.desc).append(','); } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/ClassDataJavaFileObject.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.net.URI; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; import javax.tools.JavaFileObject; /** * A JavaFileObject that reads class bytecode from .classdata files in a masked JAR. *

* This is used by MaskedJavaFileManager to provide javac with access to annotation * classes stored as .classdata files instead of .class files. * * @author Jaroslav Bachorik */ class ClassDataJavaFileObject implements JavaFileObject { private final String className; private final JarFile jarFile; private final JarEntry entry; private final String entryName; private final URI uri; /** * Creates a new ClassDataJavaFileObject. * * @param className the fully qualified class name (e.g., "org.example.MyClass") * @param jarFile the JAR file containing the .classdata file * @param entry the JAR entry for the .classdata file */ ClassDataJavaFileObject(String className, JarFile jarFile, JarEntry entry) { String name = entry.getName(); if (name.contains("..")) { throw new IllegalArgumentException("Invalid entry name (path traversal): " + name); } this.className = className; this.jarFile = jarFile; this.entry = entry; this.entryName = name; this.uri = URI.create("jar:file:" + jarFile.getName() + "!/" + name); } @Override public Kind getKind() { return Kind.CLASS; } @Override public boolean isNameCompatible(String simpleName, Kind kind) { if (kind != Kind.CLASS) { return false; } String baseName = simpleName + ".class"; return className.equals(simpleName) || className.endsWith("." + simpleName); } @Override public NestingKind getNestingKind() { return null; // Unknown } @Override public Modifier getAccessLevel() { return null; // Unknown } @Override public URI toUri() { return uri; } @Override public String getName() { // Return a .class name instead of .classdata for javac compatibility if (entryName.endsWith(".classdata")) { return entryName.substring(0, entryName.length() - ".classdata".length()) + ".class"; } return entryName; } @Override public InputStream openInputStream() throws IOException { return jarFile.getInputStream(entry); } @Override public OutputStream openOutputStream() throws IOException { throw new UnsupportedOperationException("Cannot write to .classdata file"); } @Override public Reader openReader(boolean ignoreEncodingErrors) throws IOException { throw new UnsupportedOperationException("Cannot read .classdata as text"); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { throw new UnsupportedOperationException("Cannot read .classdata as text"); } @Override public Writer openWriter() throws IOException { throw new UnsupportedOperationException("Cannot write to .classdata file"); } @Override public long getLastModified() { return entry.getTime(); } @Override public boolean delete() { return false; } /** * Reads the entire .classdata file into a byte array. * * @return the class bytecode * @throws IOException if reading fails */ byte[] getClassBytes() throws IOException { try (InputStream is = openInputStream()) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } return baos.toByteArray(); } } /** * Infers the binary name (fully qualified class name) from this file object. * * @return the binary name (e.g., "org.openjdk.btrace.core.annotations.BTrace") */ String inferBinaryName() { return className; } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Compiler.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.openjdk.btrace.core.Messages; import org.openjdk.btrace.runtime.BTraceRuntimeAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Compiler for a BTrace program. Note that a BTrace program is a Java program that is specially * annotated and can *not* use many Java constructs (essentially java--). We use JSR 199 API to * compile BTrace program but validate the program (for BTrace safety rules) using JSR 269 and * javac's Tree API. * * @author A. Sundararajan */ public class Compiler { private static final Logger log = LoggerFactory.getLogger(Compiler.class); private final CompilerHelper compilerHelper; // null means no preprocessing isf done. public List includeDirs; private final StandardJavaFileManager stdManager; private final String packExtension = "class"; public Compiler(String includePath, boolean generatePack) { if (includePath != null) { includeDirs = new ArrayList<>(); String[] paths = includePath.split(File.pathSeparator); includeDirs.addAll(Arrays.asList(paths)); } JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); stdManager = compiler.getStandardFileManager(null, null, null); compilerHelper = new CompilerHelper(compiler, generatePack); } public Compiler(String includePath) { this(includePath, false); } public Compiler(boolean generatePack) { this(null, generatePack); } public Compiler() { this(null); } private static void usage(String msg) { System.err.println(msg); System.exit(1); } private static void usage() { usage(Messages.get("btracec.usage")); } // simple test main @SuppressWarnings({"DefaultCharset", "RedundantThrows"}) public static void main(String[] args) throws Exception { if (args.length == 0) { usage(); } // turn off the unique client name generation as it is undesired during compilation try { Field f = BTraceRuntimeAccess.class.getDeclaredField("uniqueClientClassNames"); f.setAccessible(true); f.set(null, false); } catch (Exception ignored) { } String classPath = "."; String outputDir = "."; String includePath = null; boolean trusted = false; boolean generatePack = true; String packExtension = null; int count = 0; boolean classPathDefined = false; boolean outputDirDefined = false; boolean includePathDefined = false; boolean trustedDefined = false; for (; ; ) { if (args[count].charAt(0) == '-') { if (args.length <= count + 1) { usage(); } if ((args[count].equals("-cp") || args[count].equals("-classpath")) && !classPathDefined) { classPath = args[++count]; classPathDefined = true; } else if (args[count].equals("-d") && !outputDirDefined) { outputDir = args[++count]; outputDirDefined = true; } else if (args[count].equals("-I") && !includePathDefined) { includePath = args[++count]; includePathDefined = true; } else if ((args[count].equals("-unsafe") || args[count].equals("-trusted")) && !trustedDefined) { trusted = true; trustedDefined = true; } else if (args[count].equals("-nopack")) { generatePack = false; } else if (args[count].equals("-packext")) { packExtension = args[++count]; } else { usage(); } count++; if (count >= args.length) { break; } } else { break; } } if (args.length <= count) { usage(); } if (!generatePack && packExtension != null) { usage("Can not specify pack extension if not using packs (-nopack)"); } File[] files = new File[args.length - count]; for (int i = 0; i < files.length; i++) { files[i] = new File(args[i + count]); if (!files[i].exists()) { usage("File not found: " + files[i]); } } Compiler compiler = new Compiler(includePath, generatePack); classPath += File.pathSeparator + System.getProperty("java.class.path"); try { Map classes = compiler.compile(files, new PrintWriter(System.err), ".", classPath); if (classes != null) { // write .class files. for (Map.Entry c : classes.entrySet()) { String name = c.getKey().replace(".", File.separator); int index = name.lastIndexOf(File.separatorChar); String dir = outputDir + File.separator; if (index != -1) { dir += name.substring(0, index); } new File(dir).mkdirs(); String file; if (index != -1) { file = name.substring(index + 1); } else { file = name; } file += "." + (packExtension != null ? packExtension : "class"); File out = new File(dir, file); try (FileOutputStream fos = new FileOutputStream(out)) { fos.write(c.getValue()); } } } else { // fail System.exit(1); } } catch (Throwable t) { log.error("Compiler invocation failed", t); System.err.println("ERROR: Compiler failed: " + t.getMessage()); // fail System.exit(1); } } public Map compile( String fileName, String source, Writer err, String sourcePath, String classPath) { // create a new memory JavaFileManager MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager, includeDirs); // prepare the compilation unit List compUnits = new ArrayList<>(1); compUnits.add(MemoryJavaFileManager.makeStringSource(fileName, source, includeDirs)); return compile(manager, compUnits, err, sourcePath, classPath); } public Map compile(File file, Writer err, String sourcePath, String classPath) { File[] files = new File[1]; files[0] = file; return compile(files, err, sourcePath, classPath); } public Map compile( File[] files, Writer err, String sourcePath, String classPath) { Iterable compUnits = stdManager.getJavaFileObjects(files); List preprocessedCompUnits = new ArrayList<>(); try { for (JavaFileObject jfo : compUnits) { preprocessedCompUnits.add(MemoryJavaFileManager.preprocessedFileObject(jfo, includeDirs)); } } catch (IOException ioExp) { throw new RuntimeException(ioExp); } return compile(preprocessedCompUnits, err, sourcePath, classPath); } public Map compile( Iterable compUnits, Writer err, String sourcePath, String classPath) { // create a new memory JavaFileManager MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager, includeDirs); return compilerHelper.compile(manager, compUnits, err, sourcePath, classPath); } private Map compile( MemoryJavaFileManager manager, Iterable compUnits, Writer err, String sourcePath, String classPath) { // to collect errors, warnings etc. // javac options // create a compilation task // we add BTrace Verifier as a (JSR 269) Processor // print dignostics messages in case of failures. // collect .class bytes of all compiled classes return compilerHelper.compile(manager, compUnits, err, sourcePath, classPath); } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerClassWriter.java ================================================ /* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import java.io.File; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import org.objectweb.asm.ClassWriter; /** * @author Jaroslav Bachorik */ class CompilerClassWriter extends ClassWriter { private final ClassLoader cl; public CompilerClassWriter(String classPath, PrintWriter perr) { this(classPath, perr, null); } public CompilerClassWriter(String classPath, PrintWriter perr, ClassLoader maskedClassLoader) { super(ClassWriter.COMPUTE_FRAMES); if (maskedClassLoader != null) { // Use the provided MaskedClassLoader which can read .classdata files this.cl = maskedClassLoader; } else { // Fall back to URLClassLoader for standard .class files List urls = new ArrayList<>(); if (classPath != null) { for (String e : classPath.split(File.pathSeparator)) { File f = new File(e); try { urls.add(f.toURI().toURL()); } catch (MalformedURLException ex) { perr.printf("%s is not a valid classpath entry\n", e); } } } this.cl = new URLClassLoader(urls.toArray(new URL[0]), getClass().getClassLoader()); } } @Override protected String getCommonSuperClass(String type1, String type2) { Class c, d; try { c = cl.loadClass(type1.replace('/', '.')); d = cl.loadClass(type2.replace('/', '.')); } catch (Exception e) { throw new RuntimeException(e.toString()); } if (c.isAssignableFrom(d)) { return type1; } if (d.isAssignableFrom(c)) { return type2; } if (c.isInterface() || d.isInterface()) { return "java/lang/Object"; } else { do { c = c.getSuperclass(); } while (!c.isAssignableFrom(d)); return c.getName().replace('.', '/'); } } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerHelper.java ================================================ package org.openjdk.btrace.compiler; import com.sun.source.util.JavacTask; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import javax.annotation.processing.Processor; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.openjdk.btrace.boot.MaskedClassLoader; import org.openjdk.btrace.boot.MaskedJarUtils; import org.openjdk.btrace.core.SharedSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; class CompilerHelper { private static final Logger log = LoggerFactory.getLogger(CompilerHelper.class); private final boolean generatePack; // JSR 199 compiler private final JavaCompiler compiler; CompilerHelper(JavaCompiler compiler, boolean generatePack) { this.compiler = compiler; this.generatePack = generatePack; } /** * Extends the classpath with JARs from extension directories. * Scans in order: BTRACE_HOME/libs/ext/, ~/.btrace/ext/, $BTRACE_EXT_PATH * * @param classPath original classpath (may be null) * @return extended classpath including extension JARs */ private String extendClassPathWithExtensions(String classPath) { List extensionJars = new ArrayList<>(); // 1. Built-in extensions: BTRACE_HOME/libs/ext/ String btraceHome = getBTraceHome(); if (btraceHome != null) { Path builtinExtPath = Paths.get(btraceHome, "libs", "ext"); addExtensionJars(builtinExtPath, extensionJars); } // 2. User extensions: ~/.btrace/ext/ String userHome = System.getProperty("user.home"); if (userHome != null) { Path userExtPath = Paths.get(userHome, ".btrace", "ext"); addExtensionJars(userExtPath, extensionJars); } // 3. Environment variable: BTRACE_EXT_PATH String extPath = System.getenv("BTRACE_EXT_PATH"); if (extPath != null && !extPath.isEmpty()) { String[] paths = extPath.split(File.pathSeparator); for (String path : paths) { addExtensionJars(Paths.get(path), extensionJars); } } // Combine with original classpath if (extensionJars.isEmpty()) { return classPath; } StringBuilder sb = new StringBuilder(); if (classPath != null && !classPath.isEmpty()) { sb.append(classPath); } for (String jar : extensionJars) { if (sb.length() > 0) { sb.append(File.pathSeparator); } sb.append(jar); } return sb.toString(); } /** * Attempts to determine BTRACE_HOME from environment or compiler classpath. * * @return BTRACE_HOME path, or null if not found */ private String getBTraceHome() { // Try environment variable first String envHome = System.getenv("BTRACE_HOME"); if (envHome != null && Files.isDirectory(Paths.get(envHome))) { return envHome; } // Try to find from compiler classpath (look for btrace-compiler.jar or btrace-boot.jar) String classPath = System.getProperty("java.class.path"); if (classPath != null) { for (String entry : classPath.split(File.pathSeparator)) { if (entry.contains("btrace-compiler") || entry.contains("btrace-boot")) { File jarFile = new File(entry); if (jarFile.exists()) { File libsDir = jarFile.getParentFile(); if (libsDir != null && libsDir.getName().equals("libs")) { File home = libsDir.getParentFile(); if (home != null && home.isDirectory()) { return home.getAbsolutePath(); } } } } } } return null; } /** * Adds all JAR files from the given directory to the list. * * @param directory directory to scan * @param jars list to add JAR paths to */ private void addExtensionJars(Path directory, List jars) { if (!Files.isDirectory(directory)) { return; } try (DirectoryStream stream = Files.newDirectoryStream(directory, "*.jar")) { for (Path jar : stream) { jars.add(jar.toAbsolutePath().toString()); } } catch (IOException ignored) { // Directory not accessible, skip silently } } Map compile( MemoryJavaFileManager manager, Iterable compUnits, Writer err, String sourcePath, String classPath) { // Extend classpath with extension JARs classPath = extendClassPathWithExtensions(classPath); // to collect errors, warnings etc. DiagnosticCollector diagnostics = new DiagnosticCollector<>(); // javac options List options = new ArrayList<>(); options.add("-Xlint:all"); options.add("-g:lines"); options.add("-deprecation"); options.add("-source"); options.add("8"); options.add("-target"); options.add("8"); if (sourcePath != null) { options.add("-sourcepath"); options.add(sourcePath); } if (classPath != null) { options.add("-classpath"); options.add(classPath); } // Wrap the file manager with MaskedJavaFileManager if a masked JAR is on the classpath javax.tools.JavaFileManager effectiveManager = manager; File maskedJar = MaskedJarUtils.findMaskedJarInClasspath(classPath); ClassLoader maskedClassLoader = null; if (maskedJar != null) { try { effectiveManager = new MaskedJavaFileManager(manager, maskedJar); log.debug("Using MaskedJavaFileManager for: {}", maskedJar.getAbsolutePath()); // Create a MaskedClassLoader for CompilerClassWriter to use maskedClassLoader = new MaskedClassLoader(maskedJar, "client", getClass().getClassLoader()); } catch (IOException e) { log.warn("Failed to create MaskedJavaFileManager, falling back to standard manager", e); } } // create a compilation task JavacTask task = (JavacTask) compiler.getTask(err, effectiveManager, diagnostics, options, null, compUnits); Verifier btraceVerifier = new Verifier(); task.setTaskListener(btraceVerifier); // we add BTrace Verifier as a (JSR 269) Processor List processors = new ArrayList<>(1); processors.add(btraceVerifier); task.setProcessors(processors); PrintWriter perr = (err instanceof PrintWriter) ? (PrintWriter) err : new PrintWriter(err); // print dignostics messages in case of failures. if (!task.call() || containsErrors(diagnostics)) { for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { printDiagnostic(diagnostic, perr); } perr.flush(); return null; } // collect .class bytes of all compiled classes Map result = new HashMap<>(); try { Map classBytes = manager.getClassBytes(); List classNames = btraceVerifier.getClassNames(); for (String name : classNames) { if (classBytes.containsKey(name)) { dump(name + "_before", classBytes.get(name)); ClassReader cr = new ClassReader(classBytes.get(name)); ClassWriter cw = new CompilerClassWriter(classPath, perr, maskedClassLoader); cr.accept(new Postprocessor(cw), ClassReader.EXPAND_FRAMES + ClassReader.SKIP_DEBUG); byte[] classData = cw.toByteArray(); dump(name + "_after", classData); if (generatePack) { // temp hack; need turn off verifier SharedSettings.GLOBAL.setTrusted(true); String[] pathElements = classPath.split(File.pathSeparator); List urlElements = new ArrayList<>(pathElements.length); for (String pathElement : pathElements) { File f = new File(pathElement); urlElements.add(f.toURI().toURL()); } URLClassLoader generatorCL = new URLClassLoader( urlElements.toArray(new URL[0]), Compiler.class.getClassLoader()); ServiceLoader generators = ServiceLoader.load(PackGenerator.class, generatorCL); Iterator iter = generators.iterator(); if (iter.hasNext()) { PackGenerator generator = iter.next(); SharedSettings.GLOBAL.setBootClassPath(classPath); classData = generator.generateProbePack(classData); } } result.put(name, classData); } } } catch (IOException e) { log.error("Compilation failed", e); perr.append("ERROR: Compilation failed: ").append(e.getMessage()).append("\n"); } finally { // Close masked resources first (they wrap/depend on the base manager) if (maskedClassLoader instanceof AutoCloseable) { try { ((AutoCloseable) maskedClassLoader).close(); } catch (Exception ignored) { } } if (effectiveManager != manager) { try { effectiveManager.close(); } catch (IOException ignored) { } } try { manager.close(); } catch (IOException ignored) { } } return result; } private void dump(String name, byte[] code) { OutputStream os = null; try { name = name.replace(".", "_") + ".class"; File f = new File(System.getProperty("java.io.tmpdir"), name); if (!f.exists()) { f.getParentFile().createNewFile(); } os = new FileOutputStream(f); os.write(code); } catch (IOException ignored) { } finally { if (os != null) { try { os.close(); } catch (IOException ignored) { } } } } private void printDiagnostic(Diagnostic diagnostic, PrintWriter perr) { perr.println(diagnostic); } /** * Checks if the list of diagnostic messages contains at least one error. Certain {@link * JavacTask} implementations may return success error code even though errors were reported. */ private boolean containsErrors(DiagnosticCollector diagnostics) { for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { if (diagnostic.getKind() == Kind.ERROR) { return true; } } return false; } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/ConcatenatingReader.java ================================================ /* * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package org.openjdk.btrace.compiler; import java.io.BufferedReader; import java.io.FilterReader; import java.io.IOException; /** * This code is based on PCPP code from the GlueGen project. * *

A Reader implementation which finds lines ending in the backslash character ('\') and * concatenates them with the next line. * * @author Kenneth B. Russell (original author) * @author A. Sundararajan (changes documented below) *

Changes: *

* Changed the package name. * Formatted with NetBeans. */ public class ConcatenatingReader extends FilterReader { private static final String NEW_LINE = System.getProperty("line.separator"); private final BufferedReader inReader; // Any leftover characters go here private char[] curBuf; private int curPos; /** * This class requires that the input reader be a BufferedReader so it can do line-oriented * operations. */ public ConcatenatingReader(BufferedReader in) { super(in); inReader = in; } @Override public int read() throws IOException { char[] tmp = new char[1]; int num = read(tmp, 0, 1); if (num < 0) { return -1; } return tmp[0]; } // It's easier not to support mark/reset since we don't need it @Override public boolean markSupported() { return false; } @Override public void mark(int readAheadLimit) throws IOException { throw new IOException("mark/reset not supported"); } @Override public void reset() throws IOException { throw new IOException("mark/reset not supported"); } @Override public boolean ready() throws IOException { return curBuf != null || inReader.ready(); } @Override public int read(char[] cbuf, int off, int len) throws IOException { if (curBuf == null) { nextLine(); } if (curBuf == null) { return -1; } int numRead = 0; while ((len > 0) && (curBuf != null) && (curPos < curBuf.length)) { cbuf[off] = curBuf[curPos]; ++curPos; ++off; --len; ++numRead; if (curPos == curBuf.length) { nextLine(); } } return numRead; } @Override public long skip(long n) throws IOException { long numSkipped = 0; while (n > 0) { int intN = (int) n; char[] tmp = new char[intN]; int numRead = read(tmp, 0, intN); n -= numRead; numSkipped += numRead; if (numRead < intN) { break; } } return numSkipped; } private void nextLine() throws IOException { String cur = inReader.readLine(); if (cur == null) { curBuf = null; return; } // The trailing newline was trimmed by the readLine() method. See // whether we have to put it back or not, depending on whether the // last character of the line is the concatenation character. int numChars = cur.length(); boolean needNewline = true; if ((numChars > 0) && (cur.charAt(cur.length() - 1) == '\\')) { --numChars; needNewline = false; } char[] buf = new char[numChars + (needNewline ? NEW_LINE.length() : 0)]; cur.getChars(0, numChars, buf, 0); if (needNewline) { NEW_LINE.getChars(0, NEW_LINE.length(), buf, numChars); } curBuf = buf; curPos = 0; } // Test harness /* public static void main(String[] args) throws IOException { if (args.length != 1) { System.out.println("Usage: java ConcatenatingReader [file name]"); System.exit(1); } ConcatenatingReader reader = new ConcatenatingReader(new BufferedReader(new FileReader(args[0]))); OutputStreamWriter writer = new OutputStreamWriter(System.out); char[] buf = new char[8192]; boolean done = false; while (!done && reader.ready()) { int numRead = reader.read(buf, 0, buf.length); writer.write(buf, 0, numRead); if (numRead < buf.length) done = true; } writer.flush(); } */ } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/MaskedJavaFileManager.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A JavaFileManager that can read .classdata files from a masked JAR. *

* This file manager wraps another JavaFileManager and intercepts requests for class files. * When javac requests a class file (e.g., to resolve an annotation), this manager first * checks if the class exists as a .classdata file in the masked JAR's shared section. * If found, it returns a ClassDataJavaFileObject that reads from the .classdata file. * Otherwise, it delegates to the wrapped file manager. *

* This allows javac to find and process annotation classes that are stored as .classdata * files instead of .class files. * * @author Jaroslav Bachorik */ class MaskedJavaFileManager extends ForwardingJavaFileManager { private static final Logger log = LoggerFactory.getLogger(MaskedJavaFileManager.class); private static final boolean DEBUG = Boolean.getBoolean("btrace.compiler.debug"); private final File maskedJarFile; private final JarFile jarFile; /** * Creates a new MaskedJavaFileManager. * * @param fileManager the file manager to wrap * @param maskedJar the masked JAR file containing .classdata files * @throws IOException if the JAR file cannot be opened */ MaskedJavaFileManager(JavaFileManager fileManager, File maskedJar) throws IOException { super(fileManager); this.maskedJarFile = maskedJar; this.jarFile = new JarFile(maskedJar); debug("Created MaskedJavaFileManager for: " + maskedJar.getAbsolutePath()); } @Override public JavaFileObject getJavaFileForInput( Location location, String className, Kind kind) throws IOException { debug("getJavaFileForInput: location=" + location + ", className=" + className + ", kind=" + kind); // Only intercept CLASS kind requests (not SOURCE) if (kind == Kind.CLASS) { // Check if this class exists as .classdata in the shared section String classDataPath = "META-INF/btrace/shared/" + className.replace('.', '/') + ".classdata"; JarEntry entry = jarFile.getJarEntry(classDataPath); if (entry != null) { debug("Found .classdata for: " + className); return new ClassDataJavaFileObject(className, jarFile, entry); } } // Fall back to standard file manager return super.getJavaFileForInput(location, className, kind); } @Override public Iterable list( Location location, String packageName, Set kinds, boolean recurse) throws IOException { debug("list: location=" + location + ", package=" + packageName + ", kinds=" + kinds); // Get the standard list first Iterable standardFiles = super.list(location, packageName, kinds, recurse); // If not looking for CLASS files, just return standard list if (!kinds.contains(Kind.CLASS)) { return standardFiles; } List result = new ArrayList<>(); if (standardFiles != null) { for (JavaFileObject jfo : standardFiles) { result.add(jfo); } } // Add .classdata files from the shared section for this package String packagePath = packageName.replace('.', '/'); String sharedPackagePath = "META-INF/btrace/shared/" + packagePath; Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String name = entry.getName(); // Check if this entry is in the requested package if (name.startsWith(sharedPackagePath + "/") && name.endsWith(".classdata")) { // Extract the simple name and check if it's a direct child (not recursive) String relativePath = name.substring(sharedPackagePath.length() + 1); boolean isDirectChild = !relativePath.substring(0, relativePath.lastIndexOf('.')).contains("/"); if (recurse || isDirectChild) { // Convert path to class name String className = name.substring("META-INF/btrace/shared/".length()) .replace('/', '.') .replace(".classdata", ""); debug("Found .classdata in list(): " + className); result.add(new ClassDataJavaFileObject(className, jarFile, entry)); } } } return result; } @Override public String inferBinaryName(Location location, JavaFileObject file) { // If it's our custom ClassDataJavaFileObject, we know the binary name if (file instanceof ClassDataJavaFileObject) { ClassDataJavaFileObject cdfo = (ClassDataJavaFileObject) file; String binaryName = cdfo.inferBinaryName(); debug("inferBinaryName: " + binaryName + " for " + file.getName()); return binaryName; } // Otherwise delegate to parent return super.inferBinaryName(location, file); } @Override public void close() throws IOException { try { jarFile.close(); } finally { super.close(); } } private void debug(String msg) { if (DEBUG) { log.debug("[MaskedFileManager] {}", msg); } } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/MemoryJavaFileManager.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * MemoryJavaFileManager.java * @author A. Sundararajan */ package org.openjdk.btrace.compiler; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.net.URI; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.SimpleJavaFileObject; /** * JavaFileManager that keeps compiled .class bytes in memory. And also can expose input .java * "files" from Strings. * * @author A. Sundararajan */ @SuppressWarnings("RedundantThrows") public final class MemoryJavaFileManager extends ForwardingJavaFileManager { private final List includeDirs; private Map classBytes; public MemoryJavaFileManager(JavaFileManager fileManager, List includeDirs) { super(fileManager); this.includeDirs = includeDirs; classBytes = new HashMap<>(); } static JavaFileObject preprocessedFileObject(JavaFileObject fo, List includeDirs) throws IOException { if (includeDirs != null) { StringWriter out = new StringWriter(); PCPP pcpp = new PCPP(includeDirs, out); BufferedReader reader = new BufferedReader(new InputStreamReader(fo.openInputStream(), StandardCharsets.UTF_8)); pcpp.run(reader, fo.getName()); return new StringInputBuffer(fo.getName(), out.toString()); } else { return fo; } } static JavaFileObject makeStringSource(String name, String code, List includeDirs) { if (includeDirs != null) { StringWriter out = new StringWriter(); PCPP pcpp = new PCPP(includeDirs, out); try { pcpp.run(new StringReader(code), name); } catch (IOException exp) { throw new RuntimeException(exp); } return new StringInputBuffer(name, out.toString()); } else { return new StringInputBuffer(name, code); } } static URI toURI(String name) { File file = new File(name); if (file.exists()) { return file.toURI(); } else { try { return URI.create("mfm:///" + name); } catch (Exception exp) { return URI.create("mfm:///org/openjdk/btrace/script/java/java_source"); } } } public Map getClassBytes() { return classBytes; } @Override public void close() throws IOException { classBytes = new HashMap<>(); } @Override public void flush() throws IOException {} @Override public JavaFileObject getJavaFileForOutput( JavaFileManager.Location location, String className, Kind kind, FileObject sibling) throws IOException { if (kind == Kind.CLASS) { return new ClassOutputBuffer(className); } else { return super.getJavaFileForOutput(location, className, kind, sibling); } } @Override public JavaFileObject getJavaFileForInput( JavaFileManager.Location location, String className, Kind kind) throws IOException { JavaFileObject result = super.getJavaFileForInput(location, className, kind); if (kind == Kind.SOURCE) { return preprocessedFileObject(result, includeDirs); } else { return result; } } /** A file object used to represent Java source coming from a string. */ private static class StringInputBuffer extends SimpleJavaFileObject { final String code; StringInputBuffer(String name, String code) { super(toURI(name), Kind.SOURCE); this.code = code; } @Override public CharBuffer getCharContent(boolean ignoreEncodingErrors) { return CharBuffer.wrap(code); } public Reader openReader() { return new StringReader(code); } } /** A file object that stores Java bytecode into the classBytes map. */ private class ClassOutputBuffer extends SimpleJavaFileObject { private final String name; ClassOutputBuffer(String name) { super(toURI(name), Kind.CLASS); this.name = name; } @Override public OutputStream openOutputStream() { return new FilterOutputStream(new ByteArrayOutputStream()) { @Override public void close() throws IOException { out.close(); ByteArrayOutputStream bos = (ByteArrayOutputStream) out; classBytes.put(name, bos.toByteArray()); } }; } } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PCPP.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StreamTokenizer; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A minimal pseudo-C-preprocessor derived from PCPP of the GlueGen project. * * @author Kenneth B. Russell (original author) * @author A. Sundararajan (changes documented below) *

Changes: *

* Changed the package name. * Formatted with NetBeans. * Removed preservation of #define * directives. commented #defines emission. * Commented out printing of line directives. * Print * space char in output only for word tokens. This way multicharacter operators such as ==, != * etc. are property printed. */ public class PCPP { private static final Logger log = LoggerFactory.getLogger(PCPP.class); private static final boolean disableDebugPrint = true; private final Printer printer; /** * Map containing the results of #define statements. We must evaluate certain very simple * definitions. Macros and multi-f defines (which typically contain either macro definitions or * expressions) are currently not handled. */ private final Map defineMap = new HashMap<>(); private final Set nonConstantDefines = new HashSet<>(); /** List containing the #include paths as Strings */ private final List includePaths; private ParseState state; public PCPP(List includePaths) { this.includePaths = includePaths; printer = new Printer(); } public PCPP(List includePaths, Writer out) { this.includePaths = includePaths; printer = new Printer(out); } @SuppressWarnings("DefaultCharset") public static void main(String[] args) { try { Reader reader = null; String filename = null; if (args.length == 0) { usage(); } List includePaths = new ArrayList<>(); for (int i = 0; i < args.length; i++) { if (i < args.length - 1) { String arg = args[i]; if (arg.startsWith("-I")) { String[] paths = arg.substring(2).split(System.getProperty("path.separator")); includePaths.addAll(Arrays.asList(paths)); } else { usage(); } } else { String arg = args[i]; if (arg.equals("-")) { reader = new InputStreamReader(System.in); filename = "standard input"; } else { if (arg.startsWith("-")) { usage(); } filename = arg; reader = new BufferedReader(new FileReader(filename)); } } } new PCPP(includePaths).run(reader, filename); } catch (IOException e) { log.error("Preprocessor failed", e); System.err.println("ERROR: Preprocessor failed: " + e.getMessage()); } } // ---------------------------------------------------------------------- // Internals only below this point // private static void usage() { System.out.println("Usage: java PCPP [filename | -]"); System.out.println("Minimal pseudo-C-preprocessor."); System.out.println("Output goes to standard output. Standard input can be used as input"); System.out.println("by passing '-' as the argument."); System.exit(1); } public void run(Reader reader, String filename) throws IOException { StreamTokenizer tok = null; BufferedReader bufReader = null; if (reader instanceof BufferedReader) { bufReader = (BufferedReader) reader; } else { bufReader = new BufferedReader(reader); } tok = new StreamTokenizer(new ConcatenatingReader(bufReader)); tok.resetSyntax(); tok.wordChars('a', 'z'); tok.wordChars('A', 'Z'); tok.wordChars('0', '9'); tok.wordChars('_', '_'); tok.wordChars('.', '.'); tok.wordChars(128 + 32, 255); tok.whitespaceChars(0, ' '); tok.quoteChar('"'); tok.quoteChar('\''); tok.eolIsSignificant(true); tok.slashSlashComments(true); tok.slashStarComments(true); ParseState curState = new ParseState(tok, filename); ParseState oldState = state; state = curState; lineDirective(); parse(); state = oldState; if (state != null) { lineDirective(); } } public String findFile(String filename) { String sep = File.separator; for (String inclPath : includePaths) { String fullPath = inclPath + sep + filename; File file = new File(fullPath); if (file.exists()) { return fullPath; } } return null; } // Accessors private void pushBackToken() { state.tok().pushBack(); } /** Equivalent to nextToken(false) */ private int nextToken() throws IOException { return nextToken(false); } private int nextToken(boolean returnEOLs) throws IOException { int lineno = lineNumber(); // Check to see whether the previous call to nextToken() left an // EOL on the stream if (curToken() == StreamTokenizer.TT_EOL) { state.setStartOfLine(true); } else if (!state.startOfFile()) { state.setStartOfLine(false); } state.setStartOfFile(false); int val = state.tok().nextToken(); if (!returnEOLs) { if (val == StreamTokenizer.TT_EOL) { do { // Consume and return next token, setting state appropriately val = state.tok().nextToken(); state.setStartOfLine(true); printer.println(); } while (val == StreamTokenizer.TT_EOL); } } if (lineNumber() > lineno + 1) { // This is a little noisier than it needs to be, but does handle // the case of multi-line comments properly lineDirective(); } return val; } /** Reads the next token and throws an IOException if it is not the specified token character. */ private void nextRequiredToken(int requiredToken) throws IOException { int nextTok = nextToken(); if (nextTok != requiredToken) { String msg = "Expected token '" + requiredToken + "' but got "; switch (nextTok) { case StreamTokenizer.TT_EOF: msg += ""; break; case StreamTokenizer.TT_EOL: msg += ""; break; default: msg += "'" + curTokenAsString() + "'"; break; } msg += " at file " + filename() + ", line " + lineNumber(); throw new IOException(msg); } } private int curToken() { return state.tok().ttype; } private String curTokenAsString() { int t = curToken(); if (t == StreamTokenizer.TT_WORD) { return curWord(); } if (t == StreamTokenizer.TT_EOL) { throw new RuntimeException("Should not be converting EOL characters to strings"); } char c = (char) t; if (c == '"' || c == '\'') { return c + state.tok().sval + c; } return String.valueOf(c); } private String nextWord() throws IOException { int val = nextToken(); if (val != StreamTokenizer.TT_WORD) { throw new RuntimeException("Expected word at file " + filename() + ", line " + lineNumber()); } return curWord(); } private String curWord() { return state.tok().sval; } private boolean startOfLine() { return state.startOfLine(); } private String filename() { return state.filename(); } private int lineNumber() { return state.lineNumber(); } ///////////// // Parsing // ///////////// private void parse() throws IOException { int tok = 0; while ((tok = nextToken()) != StreamTokenizer.TT_EOF) { // A '#' at the beginning of a line is a preprocessor directive if (startOfLine() && (tok == '#')) { preprocessorDirective(); } else { // Output white space plus current token, handling #defines // (though not properly -- only handling #defines to constants and the empty string) // !!HACK!! - print space only for word tokens. This way multicharacter // operators such as ==, != etc. are property printed. if (tok == StreamTokenizer.TT_WORD) { printer.print(" "); } String s = curTokenAsString(); String newS = defineMap.get(s); if (newS == null) { newS = s; } printer.print(newS); } } printer.flush(); } private void preprocessorDirective() throws IOException { String w = nextWord(); boolean shouldPrint = true; switch (w) { case "define": handleDefine(); shouldPrint = false; break; case "undef": handleUndefine(); shouldPrint = false; break; case "if": case "elif": handleIf(w.equals("if")); shouldPrint = false; break; case "ifdef": case "ifndef": handleIfdef(w.equals("ifdef")); shouldPrint = false; break; case "else": handleElse(); shouldPrint = false; break; case "endif": handleEndif(); shouldPrint = false; break; case "include": handleInclude(); shouldPrint = false; break; // Unknown preprocessor directive (#pragma?) -- ignore default: break; } if (shouldPrint) { printer.print("# "); printToken(); } } //////////////////////////////////// // Handling of #define directives // //////////////////////////////////// private void handleUndefine() throws IOException { // Next token is the name of the #undef String name = nextWord(); debugPrint(true, "#undef " + name); // there shouldn't be any extra symbols after the name, but just in case... List values = new ArrayList<>(); while (nextToken(true) != StreamTokenizer.TT_EOL) { values.add(curTokenAsString()); } if (printer.enabled()) { String oldDef = defineMap.remove(name); if (oldDef == null) { System.err.println( "WARNING: ignoring redundant \"#undef " + name + "\", at \"" + filename() + "\" line " + lineNumber() + ": \"" + name + "\" was not previously defined"); } else { // System.err.println("UNDEFINED: '" + name + "' (line " + lineNumber() + " file " + // filename() + ")"); } nonConstantDefines.remove(name); } else { System.err.println( "FAILED TO UNDEFINE: '" + name + "' (line " + lineNumber() + " file " + filename() + ")"); } } private void handleDefine() throws IOException { // Next token is the name of the #define String name = nextWord(); // System.err.println("IN HANDLE_DEFINE: '" + name + "' (line " + lineNumber() + " file " + // filename() + ")"); // (Note that this is not actually proper handling for multi-line #defines) List values = new ArrayList<>(); while (nextToken(true) != StreamTokenizer.TT_EOL) { values.add(curTokenAsString()); } // if we're not within an active block of code (like inside an "#ifdef // FOO" where FOO isn't defined), then don't actually alter the definition // map. debugPrint(true, "#define " + name); if (printer.enabled()) { boolean emitDefine = true; // Handle #definitions to nothing or to a constant value int sz = values.size(); if (sz == 0) { // definition to nothing, like "#define FOO" String oldDef = defineMap.put(name, ""); if (oldDef != null) { System.err.println("WARNING: \"" + name + "\" redefined from \"" + oldDef + "\" to \"\""); } // We don't want to emit the define, because it would serve no purpose // and cause GlueGen errors (confuse the GnuCParser) emitDefine = false; // System.out.println("//---DEFINED: " + name + "to \"\""); } else if (sz == 1) { // See whether the value is a constant String value = values.get(0); if (isConstant(value)) { // Value is numeric constant like "#define FOO 5". // Put it in the #define map String oldDef = defineMap.put(name, value); if (oldDef != null) { System.err.println( "WARNING: \"" + name + "\" redefined from \"" + oldDef + "\" to \"" + value + "\""); } // System.out.println("//---DEFINED: " + name + " to \"" + value + "\""); } else { // Value is a symbolic constant like "#define FOO BAR". // Try to look up the symbol's value String newValue = resolveDefine(value, true); if (newValue != null) { // Set the value to the value of the symbol. // // TO DO: Is this correct? Why not output the symbol unchanged? // I think that it's a good thing to see that some symbols are // defined in terms of others. -chris values.set(0, newValue); } else { // Still perform textual replacement defineMap.put(name, value); nonConstantDefines.add(name); emitDefine = false; } } } else { // Non-constant define; try to do reasonable textual substitution anyway // (FIXME: should identify some of these, like (-1), as constants) emitDefine = false; StringBuilder val = new StringBuilder(); for (int i = 0; i < sz; i++) { if (i != 0) { val.append(" "); } val.append(resolveDefine(values.get(i), false)); } if (defineMap.get(name) != null) { // This is probably something the user should investigate. throw new RuntimeException( "Cannot redefine symbol \"" + name + " from \"" + defineMap.get(name) + "\" to non-constant " + " definition \"" + val + "\""); } defineMap.put(name, val.toString()); nonConstantDefines.add(name); } if (emitDefine) { // commenting out the #define in output printer.print("// "); // Print name and value printer.print("# define "); printer.print(name); for (String line : values) { printer.print(" "); printer.print(line); } printer.println(); } } // end if (enabled()) // System.err.println("OUT HANDLE_DEFINE: " + name); } private boolean isConstant(String s) { if (s.startsWith("0x") || s.startsWith("0X")) { return checkHex(s); } else { return checkDecimal(s); } } private boolean checkHex(String s) { for (int i = 2; i < s.length(); i++) { char c = s.charAt(i); if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { return false; } } return true; } private boolean checkDecimal(String s) { try { Float.valueOf(s); } catch (NumberFormatException e) { // not parsable as a number return false; } return true; } private String resolveDefine(String word, boolean returnNullIfNotFound) { String lastWord = defineMap.get(word); if (lastWord == null) { if (returnNullIfNotFound) { return null; } return word; } String nextWord = null; do { nextWord = defineMap.get(lastWord); if (nextWord != null) { lastWord = nextWord; } } while (nextWord != null); return lastWord; } /** * @param isIfdef if true, we're processing #ifdef; if false, we're processing #ifndef. */ private void handleIfdef(boolean isIfdef) throws IOException { // Next token is the name of the #ifdef String symbolName = nextWord(); debugPrint(true, (isIfdef ? "#ifdef " : "#ifndef ") + symbolName); boolean symbolIsDefined = defineMap.get(symbolName) != null; // debugPrint(true, "HANDLE_IFDEF: ifdef(" + symbolName + ") = " + symbolIsDefined ); printer.pushEnableBit(printer.enabled() && symbolIsDefined == isIfdef); } //////////////////////////////////////////////// // Handling of #if/#ifdef/ifndef/endif directives // //////////////////////////////////////////////// /** Handles #else directives */ private void handleElse() { boolean enabledStatusBeforeElse = printer.enabled(); printer.popEnableBit(); printer.pushEnableBit(printer.enabled() && !enabledStatusBeforeElse); debugPrint(true, "#else "); } private void handleEndif() { boolean enabledBeforePopping = printer.enabled(); printer.popEnableBit(); // print the endif if we were enabled prior to popEnableBit() (sending // false to debugPrint means "print regardless of current enabled() state). debugPrint(!enabledBeforePopping, "#endif/end-else"); } /** * @param isIf if true, we're processing #if; if false, we're processing #elif. */ private void handleIf(boolean isIf) throws IOException { // System.out.println("IN HANDLE_" + (isIf ? "IF" : "ELIF") + " file \"" + filename() + " line " // + lineNumber()); debugPrint(true, (isIf ? "#if" : "#elif")); boolean defineEvaluatedToTrue = handleIfRecursive(true); if (!isIf) { printer.popEnableBit(); } printer.pushEnableBit(printer.enabled() && defineEvaluatedToTrue == isIf); // System.out.println("OUT HANDLE_" + (isIf ? "IF" : "ELIF") +" (evaluated to " + // defineEvaluatedToTrue + ")"); } /** * This method is called recursively to process nested sub-expressions such as: * *

   *   #if !defined(OPENSTEP) && !(defined(NeXT) || !defined(NeXT_PDO))
   * 
* * @param greedy if true, continue evaluating sub-expressions until EOL is reached. If false, * return as soon as the first sub-expression is processed. * @return the value of the sub-expression or (if greedy==true) series of sub-expressions. */ private boolean handleIfRecursive(boolean greedy) throws IOException { // System.out.println("IN HANDLE_IF_RECURSIVE (" + ++tmp + ", greedy = " + greedy + ")"); // System.out.flush(); // ifValue keeps track of the current value of the potentially nested // "defined()" expressions as we process them. boolean ifValue = true; int openParens = 0; int tok; do { tok = nextToken(true); // System.out.println("-- READ: [" + (tok == StreamTokenizer.TT_EOL ? "" // :curTokenAsString()) + "]"); switch (tok) { case '(': ++openParens; // System.out.println("OPEN PARENS = " + openParens); ifValue = ifValue && handleIfRecursive(true); break; case ')': --openParens; // System.out.println("OPEN PARENS = " + openParens); break; case '!': { // System.out.println("HANDLE_IF_RECURSIVE HANDLING !"); boolean rhs = handleIfRecursive(false); ifValue = !rhs; // System.out.println("HANDLE_IF_RECURSIVE HANDLED OUT !, RHS = " + rhs); } break; case '&': { nextRequiredToken('&'); // System.out.println("HANDLE_IF_RECURSIVE HANDLING &&, LHS = " + ifValue); boolean rhs = handleIfRecursive(true); // System.out.println("HANDLE_IF_RECURSIVE HANDLED &&, RHS = " + rhs); ifValue = ifValue && rhs; } break; case '|': { nextRequiredToken('|'); // System.out.println("HANDLE_IF_RECURSIVE HANDLING ||, LHS = " + ifValue); boolean rhs = handleIfRecursive(true); // System.out.println("HANDLE_IF_RECURSIVE HANDLED ||, RHS = " + rhs); ifValue = ifValue || rhs; } break; case '>': case '<': case '=': { // NOTE: we don't handle expressions like this properly boolean rhs = handleIfRecursive(true); ifValue = false; } break; case StreamTokenizer.TT_WORD: { String word = curTokenAsString(); if (word.equals("defined")) { // Handle things like #if defined(SOMESYMBOL) nextRequiredToken('('); String symbol = nextWord(); boolean isDefined = defineMap.get(symbol) != null; // System.out.println("HANDLE_IF_RECURSIVE HANDLING defined(" + symbol + ") = " + // isDefined); ifValue = ifValue && isDefined; nextRequiredToken(')'); } else { // Handle things like #if SOME_SYMBOL. String symbolValue = defineMap.get(word); // See if the statement is "true"; i.e., a non-zero expression if (symbolValue != null) { // The statement is true if the symbol is defined and is a constant expression return (!nonConstantDefines.contains(word)); } else { // The statement is true if the symbol evaluates to a non-zero value // // NOTE: This doesn't yet handle evaluable expressions like "#if // SOME_SYMBOL > 5" or "#if SOME_SYMBOL == 0", both of which are // valid syntax. It only handles numeric symbols like "#if 1" try { // see if it's in decimal form return Double.parseDouble(word) != 0; } catch (NumberFormatException nfe1) { try { // ok, it's not a valid decimal value, try hex/octal value return Long.parseLong(word) != 0; } catch (NumberFormatException nfe2) { try { // ok, it's not a valid hex/octal value, try boolean return Boolean.parseBoolean(word); } catch (NumberFormatException nfe3) { // give up; the symbol isn't a numeric or boolean value return false; } } } } } } // end case TT_WORD break; case StreamTokenizer.TT_EOL: // System.out.println("HANDLE_IF_RECURSIVE HIT !"); pushBackToken(); // so caller hits EOL as well if we're recursing break; case StreamTokenizer.TT_EOF: throw new RuntimeException( "Unexpected end of file while parsing " + "#if statement at file " + filename() + ", line " + lineNumber()); default: throw new RuntimeException( "Unexpected token (" + curTokenAsString() + ") while parsing " + "#if statement at file " + filename() + ", line " + lineNumber()); } // System.out.println("END OF WHILE: greedy = " + greedy + " parens = " +openParens + " not // EOL = " + (tok != StreamTokenizer.TT_EOL) + " --> " + ((greedy && openParens >= 0) && tok // != StreamTokenizer.TT_EOL)); } while ((greedy && openParens >= 0) && tok != StreamTokenizer.TT_EOL); // System.out.println("OUT HANDLE_IF_RECURSIVE (" + tmp-- + ", returning " + ifValue + ")"); // System.out.flush(); return ifValue; } // static int tmp = -1; ///////////////////////////////////// // Handling of #include directives // ///////////////////////////////////// @SuppressWarnings("DefaultCharset") private void handleInclude() throws IOException { // Two kinds of #includes: one with quoted string for argument, // one with angle brackets surrounding argument int t = nextToken(); String filename = null; if (t == '"') { filename = curWord(); } else if (t == '<') { // Components of path name are coming in as separate tokens; // concatenate them StringBuilder buf = new StringBuilder(); while ((t = nextToken()) != '>' && (t != StreamTokenizer.TT_EOF)) { buf.append(curTokenAsString()); } if (t == StreamTokenizer.TT_EOF) { System.err.println("WARNING: unexpected EOF while processing #include directive"); } filename = buf.toString(); } // if we're not within an active block of code (like inside an "#ifdef // FOO" where FOO isn't defined), then don't actually process the // #included file. debugPrint(true, "#include [" + filename + "]"); if (printer.enabled()) { // Look up file in known #include path String fullname = findFile(filename); // System.out.println("ACTIVE BLOCK, LOADING " + filename); if (fullname == null) { System.err.println("WARNING: unable to find #include file \"" + filename + "\""); return; } // Process this file in-line Reader reader = new BufferedReader(new FileReader(fullname)); run(reader, fullname); } else { // System.out.println("INACTIVE BLOCK, SKIPPING " + filename); } } private void debugPrint(boolean onlyPrintIfEnabled, String msg) { if (disableDebugPrint) { return; } if (!onlyPrintIfEnabled || printer.enabled()) { for (int i = Printer.debugPrintIndentLevel; --i > 0; ) { System.out.print(" "); } System.out.println(msg + " (line " + lineNumber() + " file " + filename() + ")"); } } private void printToken() { printer.print(curTokenAsString()); } private void lineDirective() { /* * Originally this code was emitting line directives. We don't need those. * * print("# " + lineNumber() + " \"" + filename() + "\""); * println(); */ } // State static class ParseState { private final StreamTokenizer tok; private final String filename; // We do not generate #line directives // private int lineNumber; private boolean startOfLine; private boolean startOfFile; ParseState(StreamTokenizer tok, String filename) { this.tok = tok; this.filename = filename; // We do not generate #line directives // lineNumber = 1; startOfLine = true; startOfFile = true; } StreamTokenizer tok() { return tok; } String filename() { return filename; } int lineNumber() { return tok.lineno(); } boolean startOfLine() { return startOfLine; } void setStartOfLine(boolean val) { startOfLine = val; } boolean startOfFile() { return startOfFile; } void setStartOfFile(boolean val) { startOfFile = val; } } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PackGenerator.java ================================================ package org.openjdk.btrace.compiler; import java.io.IOException; public interface PackGenerator { byte[] generateProbePack(byte[] data) throws IOException; } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Postprocessor.java ================================================ /* * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; /** * @author Jaroslav Bachorik */ public class Postprocessor extends ClassVisitor { private final List fields = new ArrayList<>(); private boolean shortSyntax = false; private String className = ""; public Postprocessor(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { if (((access & Opcodes.ACC_PUBLIC) | (access & Opcodes.ACC_PROTECTED) | (access & Opcodes.ACC_PRIVATE)) == 0) { shortSyntax = true; // specifying "class " rather than "public class " means using // short syntax access |= Opcodes.ACC_PUBLIC; // force the public modifier on the btrace class } className = name; super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { if (!shortSyntax) return super.visitMethod(access, name, desc, signature, exceptions); if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE)) == 0) { access &= ~Opcodes.ACC_PROTECTED; access |= Opcodes.ACC_PUBLIC; } int localVarOffset = ((access & Opcodes.ACC_STATIC) == 0) ? -1 : 0; access |= Opcodes.ACC_STATIC; boolean isconstructor = false; if ("".equals(name)) { name = ""; isconstructor = true; } if (isconstructor) { // the script class needs as dummy default constructor createDefaultConstructor(); } return new MethodConvertor( localVarOffset, isconstructor, super.visitMethod(access, name, desc, signature, exceptions)); } private void createDefaultConstructor() { MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); // load 'this' mv.visitVarInsn(Opcodes.ALOAD, 0); // invoke super constructor mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); mv.visitMaxs(1, 1); mv.visitEnd(); } @Override public FieldVisitor visitField( int access, String name, String desc, String signature, Object value) { if (!shortSyntax) return super.visitField(access, name, desc, signature, value); List attrs = new ArrayList<>(); return new FieldVisitor(Opcodes.ASM9) { private final List annotations = new ArrayList<>(); @Override public AnnotationVisitor visitAnnotation(String type, boolean visible) { AnnotationDef ad = new AnnotationDef(type); annotations.add(ad); return new AnnotationVisitor(Opcodes.ASM9, super.visitAnnotation(type, visible)) { @Override public void visit(String name, Object val) { ad.addValue(name, val); super.visit(name, val); } }; } @Override public void visitAttribute(Attribute atrbt) { super.visitAttribute(atrbt); attrs.add(atrbt); } @Override public void visitEnd() { FieldDescriptor fd = new FieldDescriptor(access, name, desc, signature, value, attrs, annotations); fields.add(fd); super.visitEnd(); } }; } @Override public void visitEnd() { if (shortSyntax) { addFields(); } } private void addFields() { for (FieldDescriptor fd : fields) { String fieldName = fd.name; int fieldAccess = fd.access; String fieldDesc = fd.desc; String fieldSignature = fd.signature; Object fieldValue = fd.value; fieldAccess &= ~Opcodes.ACC_PRIVATE; fieldAccess &= ~Opcodes.ACC_PROTECTED; fieldAccess |= Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; FieldVisitor fv = super.visitField(fieldAccess, fieldName, fieldDesc, fieldSignature, fieldValue); for (AnnotationDef ad : fd.annotations) { AnnotationVisitor av = fv.visitAnnotation(ad.getType(), true); for (Map.Entry attr : ad.getValues().entrySet()) { av.visit(attr.getKey(), attr.getValue()); } } for (Attribute attr : fd.attributes) { fv.visitAttribute(attr); } fv.visitEnd(); } } private static final class AnnotationDef { private final String type; private final Map values = new HashMap<>(); public AnnotationDef(String type) { this.type = type; } public void addValue(String name, Object val) { values.put(name, val); } public String getType() { return type; } public Map getValues() { return values; } } private static class FieldDescriptor { final int access; final String name, desc, signature; final Object value; final List attributes; final List annotations; int var = -1; boolean initialized; FieldDescriptor( int acc, String n, String d, String sig, Object val, List attrs, List annots) { access = acc; name = n; desc = d; signature = sig; value = val; attributes = attrs; annotations = annots; } } private class MethodConvertor extends MethodVisitor { private final Deque simulatedStack = new ArrayDeque<>(); private final boolean isConstructor; private int localVarOffset = 0; private boolean copyEnabled = false; public MethodConvertor(int localVarOffset, boolean isConstructor, MethodVisitor mv) { super(Opcodes.ASM9, mv); this.localVarOffset = localVarOffset; this.isConstructor = isConstructor; copyEnabled = !isConstructor; // copy is enabled by default for all methods except constructor } @Override public void visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index) { if (index + localVarOffset < 0 || !copyEnabled) { return; } super.visitLocalVariable(name, desc, signature, start, end, index + localVarOffset); } @Override public void visitVarInsn(int opcode, int var) { boolean delegate = true; switch (opcode) { case Opcodes.ALOAD: { delegate = (var + localVarOffset) >= 0; simulatedStack.push(!delegate); break; } case Opcodes.LLOAD: case Opcodes.DLOAD: { simulatedStack.push(Boolean.FALSE); // long and double occupy 2 stack slots; fall through } case Opcodes.ILOAD: case Opcodes.FLOAD: { simulatedStack.push(Boolean.FALSE); break; } case Opcodes.LSTORE: case Opcodes.DSTORE: { simulatedStack.poll(); // long and double occupy 2 stack slots; fall through } case Opcodes.ASTORE: case Opcodes.ISTORE: case Opcodes.FSTORE: { simulatedStack.poll(); break; } } if (delegate && copyEnabled) super.visitVarInsn(opcode, var + localVarOffset); } @Override public void visitInsn(int opcode) { switch (opcode) { case Opcodes.POP: { if (simulatedStack.pop()) { return; } break; } case Opcodes.POP2: { Boolean[] vals = new Boolean[2]; vals[0] = simulatedStack.poll(); vals[1] = simulatedStack.poll(); if (vals[0] && vals[1]) { return; } else if (vals[0] || vals[1]) { opcode = Opcodes.POP; } break; } case Opcodes.DUP: { Boolean val = simulatedStack.peek(); val = val != null ? val : Boolean.FALSE; simulatedStack.push(val); if (val) return; break; } case Opcodes.DUP_X1: { if (simulatedStack.size() < 2) return; Boolean[] vals = new Boolean[2]; int cntr = vals.length - 1; while (cntr >= 0) { vals[cntr--] = simulatedStack.pop(); } simulatedStack.push(vals[vals.length - 1]); simulatedStack.addAll(Arrays.asList(vals)); if (vals[1]) { return; } else if (vals[0]) { opcode = Opcodes.DUP; } break; } case Opcodes.DUP_X2: { if (simulatedStack.size() < 3) return; Boolean[] vals = new Boolean[3]; int cntr = vals.length - 1; while (cntr >= 0) { vals[cntr--] = simulatedStack.pop(); } simulatedStack.push(vals[vals.length - 1]); simulatedStack.addAll(Arrays.asList(vals)); if (vals[2]) { return; } else if (vals[0] && vals[1]) { opcode = Opcodes.DUP; } else { opcode = Opcodes.DUP_X1; } break; } case Opcodes.DUP2: { if (simulatedStack.size() < 2) return; Boolean[] vals = new Boolean[2]; int cntr = vals.length - 1; while (cntr >= 0) { vals[cntr--] = simulatedStack.pop(); } simulatedStack.addAll(Arrays.asList(vals)); if (vals[0] && vals[1]) { return; } else if (vals[0] || vals[1]) { opcode = Opcodes.DUP; } break; } case Opcodes.DUP2_X1: { if (simulatedStack.size() < 3) return; Boolean[] vals = new Boolean[3]; int cntr = vals.length - 1; while (cntr >= 0) { vals[cntr--] = simulatedStack.pop(); } simulatedStack.push(vals[vals.length - 2]); simulatedStack.push(vals[vals.length - 1]); simulatedStack.addAll(Arrays.asList(vals)); if (vals[1] && vals[2]) { return; } if (vals[0]) { if (vals[1] || vals[2]) { opcode = Opcodes.DUP; } else { opcode = Opcodes.DUP2; } } else { if (vals[1] || vals[2]) { opcode = Opcodes.DUP_X1; } } break; } case Opcodes.DUP2_X2: { Boolean[] vals = {Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE}; Iterator iter = simulatedStack.descendingIterator(); int cntr = 0; while (cntr < vals.length && iter.hasNext()) { vals[cntr++] = iter.next(); } simulatedStack.push(vals[vals.length - 2]); simulatedStack.push(vals[vals.length - 1]); simulatedStack.addAll(Arrays.asList(vals)); break; } case Opcodes.SWAP: { if (simulatedStack.size() < 2) return; Boolean[] vals = new Boolean[2]; int cntr = vals.length - 1; while (cntr >= 0) { vals[cntr--] = simulatedStack.pop(); } if (vals[0] || vals[1]) { return; } simulatedStack.push(vals[1]); simulatedStack.push(vals[0]); break; } // zero operand instructions case Opcodes.LCONST_0: case Opcodes.LCONST_1: case Opcodes.DCONST_0: case Opcodes.DCONST_1: { simulatedStack.push(Boolean.FALSE); // the value occupies 2 slots on stack // fall through } case Opcodes.ICONST_0: case Opcodes.ICONST_1: case Opcodes.ICONST_2: case Opcodes.ICONST_3: case Opcodes.ICONST_4: case Opcodes.ICONST_5: case Opcodes.ICONST_M1: case Opcodes.FCONST_0: case Opcodes.FCONST_1: case Opcodes.FCONST_2: case Opcodes.ACONST_NULL: case Opcodes.MONITORENTER: case Opcodes.MONITOREXIT: { simulatedStack.push(Boolean.FALSE); break; } // one operand instructions case Opcodes.INEG: case Opcodes.FNEG: case Opcodes.DNEG: case Opcodes.LNEG: case Opcodes.I2B: case Opcodes.I2C: case Opcodes.I2F: case Opcodes.I2S: case Opcodes.L2D: case Opcodes.D2L: case Opcodes.F2I: case Opcodes.CHECKCAST: case Opcodes.ARRAYLENGTH: { // nothing changes in regard to the simulated stack break; } case Opcodes.I2L: case Opcodes.I2D: { simulatedStack.push(Boolean.FALSE); // extending the original value by one slot break; } // two operand instructions case Opcodes.LADD: case Opcodes.DADD: case Opcodes.LSUB: case Opcodes.DSUB: case Opcodes.LMUL: case Opcodes.DMUL: case Opcodes.LDIV: case Opcodes.DDIV: case Opcodes.LREM: case Opcodes.DREM: case Opcodes.LSHL: case Opcodes.LSHR: case Opcodes.LUSHR: case Opcodes.LAND: case Opcodes.LOR: case Opcodes.LXOR: case Opcodes.LALOAD: case Opcodes.DALOAD: { simulatedStack.pop(); simulatedStack.pop(); // remove 4 slots == 2 long/double operands and add 2 slots == 1 long/double result break; } case Opcodes.LCMP: case Opcodes.DCMPL: case Opcodes.DCMPG: { simulatedStack.pop(); simulatedStack.pop(); simulatedStack.pop(); // remove 4 slots == 2 long/double operands and add 1 slot == 1 int result break; } case Opcodes.IADD: case Opcodes.FADD: case Opcodes.ISUB: case Opcodes.FSUB: case Opcodes.IMUL: case Opcodes.IDIV: case Opcodes.FDIV: case Opcodes.IREM: case Opcodes.FREM: case Opcodes.ISHL: case Opcodes.ISHR: case Opcodes.IUSHR: case Opcodes.IAND: case Opcodes.IOR: case Opcodes.IXOR: case Opcodes.FCMPL: case Opcodes.FCMPG: case Opcodes.BALOAD: case Opcodes.SALOAD: case Opcodes.CALOAD: case Opcodes.IALOAD: case Opcodes.FALOAD: { simulatedStack.poll(); // remove 2 slots == 2 intoperands and add 1 slot == 1 int result break; } // three operand instructions case Opcodes.LASTORE: case Opcodes.DASTORE: { simulatedStack.pop(); // LASTORE, DSTORE occupy one more slot compared to BASTORE etc.; falling through // fall through } case Opcodes.BASTORE: // fall through case Opcodes.CASTORE: // fall through case Opcodes.SASTORE: // fall through case Opcodes.IASTORE: // fall through case Opcodes.FASTORE: { simulatedStack.pop(); simulatedStack.pop(); simulatedStack.pop(); } } if (copyEnabled) { super.visitInsn(opcode); } } @Override public void visitIntInsn(int opcode, int index) { switch (opcode) { case Opcodes.BIPUSH: case Opcodes.SIPUSH: { simulatedStack.push(Boolean.FALSE); break; } } if (copyEnabled) { super.visitIntInsn(opcode, index); } } @Override public void visitJumpInsn(int opcode, Label label) { switch (opcode) { case Opcodes.IFEQ: case Opcodes.IFNE: case Opcodes.IFLE: case Opcodes.IFGE: case Opcodes.IFGT: case Opcodes.IFLT: case Opcodes.IFNULL: case Opcodes.IFNONNULL: { simulatedStack.poll(); break; } case Opcodes.IF_ICMPEQ: case Opcodes.IF_ICMPGE: case Opcodes.IF_ICMPGT: case Opcodes.IF_ICMPLE: case Opcodes.IF_ICMPLT: case Opcodes.IF_ICMPNE: { simulatedStack.poll(); simulatedStack.poll(); break; } } if (copyEnabled) { super.visitJumpInsn(opcode, label); } } @Override public void visitTableSwitchInsn(int i, int i1, Label label, Label... labels) { simulatedStack.poll(); if (copyEnabled) { super.visitTableSwitchInsn(i, i1, label, labels); } } @Override public void visitLookupSwitchInsn(Label label, int[] ints, Label[] labels) { simulatedStack.poll(); if (copyEnabled) { super.visitLookupSwitchInsn(label, ints, labels); } } @Override public void visitLdcInsn(Object o) { simulatedStack.push(Boolean.FALSE); if (o instanceof Long || o instanceof Double) { simulatedStack.push(Boolean.FALSE); } if (copyEnabled) { super.visitLdcInsn(o); } } @Override public void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(maxStack, (Math.max(maxLocals + localVarOffset, 0))); } @Override public void visitIincInsn(int var, int increment) { if (copyEnabled) { super.visitIincInsn(var + localVarOffset, increment); } } @Override public void visitFieldInsn(int i, String clazz, String name, String desc) { if (i == Opcodes.GETFIELD) { Boolean opTarget = simulatedStack.poll(); opTarget = opTarget != null ? opTarget : Boolean.FALSE; if (opTarget) { i = Opcodes.GETSTATIC; } } else if (i == Opcodes.PUTFIELD) { simulatedStack.pop(); simulatedStack.pop(); if (desc.equals("J") || desc.equals("D")) { simulatedStack.pop(); } if (clazz.equals(className)) { // all local fields are static i = Opcodes.PUTSTATIC; } } switch (i) { case Opcodes.GETFIELD: case Opcodes.GETSTATIC: { simulatedStack.push(Boolean.FALSE); if (desc.equals("J") || desc.equals("D")) { simulatedStack.push(Boolean.FALSE); } break; } } if (copyEnabled) { super.visitFieldInsn(i, clazz, name, desc); } } @Override public void visitMethodInsn( int opcode, String clazz, String method, String desc, boolean iface) { int origOpcode = opcode; Type[] args = Type.getArgumentTypes(desc); for (Type t : args) { for (int i = 0; i < t.getSize(); i++) { simulatedStack.poll(); } } if (opcode != Opcodes.INVOKESTATIC) { Boolean targetVal = simulatedStack.poll(); if (targetVal != null && targetVal) { // "true" on stack means the original reference to "this" opcode = Opcodes.INVOKESTATIC; } } if (!Type.getReturnType(desc).equals(Type.VOID_TYPE)) { simulatedStack.push(Boolean.FALSE); } if (!copyEnabled) { if (origOpcode == Opcodes.INVOKESPECIAL && isConstructor) { copyEnabled = true; } } else { super.visitMethodInsn(opcode, clazz, method, desc, iface); } } @Override public AnnotationVisitor visitAnnotation(String string, boolean bln) { return copyEnabled ? super.visitAnnotation(string, bln) : new AnnotationVisitor(Opcodes.ASM9) {}; } @Override public AnnotationVisitor visitAnnotationDefault() { return copyEnabled ? super.visitAnnotationDefault() : new AnnotationVisitor(Opcodes.ASM9) {}; } @Override public void visitAttribute(Attribute atrbt) { if (copyEnabled) { super.visitAttribute(atrbt); } } @Override public void visitMultiANewArrayInsn(String string, int i) { for (int ind = 0; ind < i; ind++) { simulatedStack.pop(); } simulatedStack.push(Boolean.FALSE); if (copyEnabled) { super.visitMultiANewArrayInsn(string, i); } } @Override public AnnotationVisitor visitParameterAnnotation(int i, String string, boolean bln) { return copyEnabled ? super.visitParameterAnnotation(i, string, bln) : new AnnotationVisitor(Opcodes.ASM9) {}; } @Override public void visitTypeInsn(int opcode, String typeName) { switch (opcode) { case Opcodes.NEW: { simulatedStack.push(Boolean.FALSE); break; } } if (copyEnabled) { super.visitTypeInsn(opcode, typeName); } } } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Printer.java ================================================ package org.openjdk.btrace.compiler; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; class Printer { static int debugPrintIndentLevel = 0; //////////// // Output // //////////// private final PrintWriter writer; private final ArrayList enabledBits = new ArrayList<>(); Printer() { writer = new PrintWriter(System.err); } Printer(Writer out) { writer = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out); } public static int getDebugPrintIndentLevel() { return debugPrintIndentLevel; } void println() { if (enabled()) { writer.println(); } } boolean enabled() { return (enabledBits.isEmpty() || enabledBits.get(enabledBits.size() - 1)); } void pushEnableBit(boolean enabled) { enabledBits.add(enabled); ++debugPrintIndentLevel; // debugPrint(false, "PUSH_ENABLED, NOW: " + enabled()); } void print(String s) { if (enabled()) { writer.print(s); // System.out.print(s);//debug } } void flush() { if (enabled()) { writer.flush(); // System.err.flush(); //debug } } void popEnableBit() { if (enabledBits.isEmpty()) { System.err.println("WARNING: mismatched #ifdef/endif pairs"); return; } enabledBits.remove(enabledBits.size() - 1); --debugPrintIndentLevel; // debugPrint(false, "POP_ENABLED, NOW: " + enabled()); } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Verifier.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; import com.sun.source.util.JavacTask; import com.sun.source.util.SourcePositions; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import org.openjdk.btrace.core.Messages; import org.openjdk.btrace.core.annotations.BTrace; /** * An annotation processor that validates a BTrace program. Safety rules (such as no loops, no * new/throw etc.) are enforced. This uses javac's Tree API in addition to JSR 269. * * @author A. Sundararajan */ @SupportedAnnotationTypes("*") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class Verifier extends AbstractProcessor implements TaskListener { private final List classNames = new ArrayList<>(); private final List compUnits = new ArrayList<>(); private final AttributionTaskListener listener = new AttributionTaskListener(); private Trees treeUtils; private ClassTree currentClass; @Override public synchronized void init(ProcessingEnvironment pe) { super.init(pe); treeUtils = Trees.instance(pe); JavacTask task = JavacTask.instance(processingEnv); task.addTaskListener(listener); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { return true; } @Override public void started(TaskEvent e) { if (e.getKind() == TaskEvent.Kind.ENTER) { CompilationUnitTree ct = e.getCompilationUnit(); if (ct != null) { compUnits.add(ct); } } } @Override public void finished(TaskEvent e) { if (e.getKind() != TaskEvent.Kind.ANALYZE) return; if (processingEnv == null) { return; } TypeElement elem = e.getTypeElement(); for (Tree t : e.getCompilationUnit().getTypeDecls()) { TreePath topLevel = new TreePath(e.getCompilationUnit()); if (t.getKind() == Tree.Kind.CLASS) { if (elem.equals(getTreeUtils().getElement(new TreePath(topLevel, t)))) { currentClass = (ClassTree) t; break; } } } if (currentClass != null) { verify(currentClass, elem); } } List getClassNames() { return classNames; } CompilationUnitTree getCompilationUnit() { for (CompilationUnitTree ct : compUnits) { for (Tree clazz : ct.getTypeDecls()) { if (clazz.equals(currentClass)) { return ct; } } } return null; } Trees getTreeUtils() { return treeUtils; } SourcePositions getSourcePositions() { return treeUtils.getSourcePositions(); } ProcessingEnvironment getProcessingEnvironment() { return processingEnv; } Messager getMessager() { return processingEnv.getMessager(); } Elements getElementUtils() { return processingEnv.getElementUtils(); } Types getTypeUtils() { return processingEnv.getTypeUtils(); } Locale getLocale() { return processingEnv.getLocale(); } String annotationName(AnnotationTree at) { TreePath tp = getTreeUtils().getPath(getCompilationUnit(), at.getAnnotationType()); Element el = getTreeUtils().getElement(tp); if (el == null || el.getKind() != ElementKind.ANNOTATION_TYPE) { return null; } return ((TypeElement) el).getQualifiedName().toString(); } // verify each BTrace class private void verify(ClassTree ct, Element topElement) { currentClass = ct; CompilationUnitTree cut = getCompilationUnit(); String className = ct.getSimpleName().toString(); ExpressionTree pkgName = cut.getPackageName(); if (pkgName != null) { className = pkgName + "." + className; } classNames.add(className); if (hasTrustedAnnotation(ct, topElement)) { return; } ct.accept(new VerifierVisitor(this, topElement), null); } /** Detects if the class is annotated as @BTrace(trusted=true). */ private boolean hasTrustedAnnotation(ClassTree ct, Element topElement) { for (AnnotationTree at : ct.getModifiers().getAnnotations()) { String annFqn = annotationName(at); if (!BTrace.class.getName().equals(annFqn)) { continue; } // now we have @BTrace, look for unsafe = xxx or trusted = xxx for (ExpressionTree ext : at.getArguments()) { if (ext.getKind() != Tree.Kind.ASSIGNMENT) { continue; } AssignmentTree assign = (AssignmentTree) ext; String name = ((IdentifierTree) assign.getVariable()).getName().toString(); if (!"unsafe".equals(name) && !"trusted".equals(name)) { continue; } // now rhs is the value of @BTrace.unsafe. // The value can be complex (!!true, 1 == 2, etc.) - we support only booleans String val = assign.getExpression().toString(); if ("true".equals(val)) { return true; // bingo! } else if (!"false".equals(val)) { processingEnv .getMessager() .printMessage(Kind.WARNING, Messages.get("no.complex.unsafe.value"), topElement); } } } return false; } /** A task listener that invokes the processor whenever a class is fully analyzed. */ private final class AttributionTaskListener implements TaskListener { @Override public void finished(TaskEvent e) { if (e.getKind() != TaskEvent.Kind.ANALYZE) return; TypeElement elem = e.getTypeElement(); TreePath topLevel = new TreePath(e.getCompilationUnit()); for (Tree t : e.getCompilationUnit().getTypeDecls()) { if (t.getKind() == Tree.Kind.CLASS) { if (elem.equals(getTreeUtils().getElement(new TreePath(topLevel, t)))) { currentClass = (ClassTree) t; break; } } } if (currentClass != null) { verify(currentClass, elem); } } @Override public void started(TaskEvent e) {} } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/VerifierVisitor.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.compiler; import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.AssertTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.CatchTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.DoWhileLoopTree; import com.sun.source.tree.EnhancedForLoopTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.ForLoopTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ModifiersTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.ReturnTree; import com.sun.source.tree.SynchronizedTree; import com.sun.source.tree.ThrowTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TryTree; import com.sun.source.tree.VariableTree; import com.sun.source.tree.WhileLoopTree; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; import com.sun.source.util.TreeScanner; import org.openjdk.btrace.core.Messages; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Injected; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.OnError; import org.openjdk.btrace.core.annotations.OnExit; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Sampled; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.EnumSet; import org.openjdk.btrace.core.extensions.Permission; /** * This class tree visitor validates a BTrace program's ClassTree. * * @author A. Sundararajan */ public class VerifierVisitor extends TreeScanner { private static final String ON_ERROR_TYPE = OnError.class.getName(); private static final String ON_EXIT_TYPE = OnExit.class.getName(); private static final String THROWABLE_TYPE = Throwable.class.getName(); private final Verifier verifier; private String className; private String fqn; private boolean insideMethod; private boolean shortSyntax = false; // Legacy service type mirrors removed. Only Extension-based checks remain. private TypeMirror extensionTm = null; private boolean isInAnnotation = false; private final Set eventFieldNames = new HashSet<>(); private final Set servicePackages = new HashSet<>(); private final Set injectedServiceTypes = new HashSet<>(); /** Permissions required by extensions used in this probe */ private final EnumSet requiredPermissions = EnumSet.noneOf(Permission.class); // Probe-declared permissions removed; permissions enforced via manifest/runtime grants. private final TreeScanner jfrFieldNameCollector = new TreeScanner() { @Override public Void visitAnnotation(AnnotationTree node, Void o) { String annType = node.getAnnotationType().toString(); if (annType.endsWith("Event")) { for (ExpressionTree et : node.getArguments()) { AssignmentTree t = (AssignmentTree) et; String name = t.getVariable().toString(); if (name.equals("fields")) { processEventFields(t); } } } return super.visitAnnotation(node, o); } }; public VerifierVisitor(Verifier verifier, Element clzElement) { this.verifier = verifier; // Legacy service types are no longer referenced at compile time. TypeElement extensionElement = verifier.getElementUtils().getTypeElement("org.openjdk.btrace.core.extensions.Extension"); if (extensionElement != null) { extensionTm = extensionElement.asType(); } } @Override public Void visitMethodInvocation(MethodInvocationTree node, Void v) { Element e = getElement(node); if (e != null && (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR)) { String name = e.getSimpleName().toString(); // allow constructor calls if (name.equals("")) { return super.visitMethodInvocation(node, v); } TypeElement parent = null; do { parent = (TypeElement) e.getEnclosingElement(); } while (parent != null && (parent.getKind() != ElementKind.CLASS && parent.getKind() != ElementKind.INTERFACE)); if (parent != null) { TypeMirror tm = parent.asType(); String typeName = tm.toString(); if (isSameClass(typeName)) { return super.visitMethodInvocation(node, v); } if (isBTraceClass(typeName)) { if (typeName.contains("BTraceUtils")) { if (e.getSimpleName().contentEquals("setEventField")) { String nameValue = node.getArguments().get(1).toString(); if (!eventFieldNames.contains(nameValue)) { reportError("jfr.event.invalid.field", node.getArguments().get(1)); } } } return super.visitMethodInvocation(node, v); } // Allow extension APIs and injected service-derived types // Allow direct calls on injected extensions as well if (extensionTm != null && verifier.getTypeUtils().isSubtype(tm, extensionTm)) { return super.visitMethodInvocation(node, v); } // Also allow calls on any field types annotated with @Injected // and on types in the same package/sub-packages as injected services if (injectedServiceTypes.contains(typeName) || isServiceDerivedType(typeName)) { return super.visitMethodInvocation(node, v); } } } reportError("no.method.calls", node); return super.visitMethodInvocation(node, v); } private boolean isServiceDerivedType(String typeName) { // Check if this type is in the same package as any registered service // This makes the verifier cooperate with the extension system automatically for (String servicePackage : servicePackages) { if (typeName.startsWith(servicePackage + ".")) { return true; } } return false; } private boolean isSameClass(String typeName) { return fqn.equals(typeName); } private boolean isBTraceClass(String typeName) { return typeName.equals("org.openjdk.btrace.core.BTraceUtils") || typeName.startsWith("org.openjdk.btrace.core.BTraceUtils."); } @Override public Void visitAssert(AssertTree node, Void v) { reportError("no.asserts", node); return super.visitAssert(node, v); } @Override public Void visitAssignment(AssignmentTree node, Void v) { checkLValue(node.getVariable()); return super.visitAssignment(node, v); } @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, Void v) { checkLValue(node.getVariable()); return super.visitCompoundAssignment(node, v); } @Override public Void visitCatch(CatchTree node, Void v) { reportError("no.catch", node); return super.visitCatch(node, v); } @Override public Void visitClass(ClassTree node, Void v) { // check for local class if (insideMethod) { reportError("no.local.class", node); } // check for short BTrace syntax (inferring redundant access qualifiers) Set mods = node.getModifiers().getFlags(); if (!mods.contains(Modifier.PRIVATE) && !mods.contains(Modifier.PROTECTED) && !mods.contains(Modifier.PUBLIC)) { shortSyntax = true; } // check for inner and nested class List members = node.getMembers(); for (Tree m : members) { if (m.getKind() == Tree.Kind.CLASS) { reportError("no.nested.class", m); } if (m.getKind() == Tree.Kind.VARIABLE) { VariableTree vt = (VariableTree) m; boolean isStatic = isStatic(vt.getModifiers().getFlags()); if (shortSyntax) { if (isStatic) { reportError("no.static.variables", m); } } else { if (!isStatic) { reportError("no.instance.variables", m); } } } } // should extend java.lang.Object Tree superClass = node.getExtendsClause(); if (superClass != null) { String name = superClass.toString(); if (!name.equals("Object") && !name.equals("java.lang.Object")) { reportError("object.superclass.required", superClass); } } // should not implement interfaces List interfaces = node.getImplementsClause(); if (interfaces != null && interfaces.size() > 0) { reportError("no.interface.implementation", interfaces.get(0)); } ModifiersTree mt = node.getModifiers(); if (!shortSyntax && !isPublic(mt.getFlags())) { reportError("class.should.be.public", node); } List anno = mt.getAnnotations(); if (anno != null && !anno.isEmpty()) { String btrace = BTrace.class.getName(); boolean isBTrace = false; for (AnnotationTree at : anno) { String name = at.getAnnotationType().toString(); if (name.equals(btrace) || name.equals("BTrace")) { isBTrace = true; } // RequestPermission annotations removed; nothing to collect here. } if (isBTrace) { String oldClassName = className; try { className = node.getSimpleName().toString(); fqn = getElement(node).asType().toString(); Void result = super.visitClass(node, v); // Permissions are enforced against agent grants at runtime. return result; } finally { className = oldClassName; } } } reportError("not.a.btrace.program", node); return null; } @Override public Void visitDoWhileLoop(DoWhileLoopTree node, Void v) { reportError("no.do.while", node); return super.visitDoWhileLoop(node, v); } @Override public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void v) { reportError("no.enhanced.for", node); return super.visitEnhancedForLoop(node, v); } @Override public Void visitForLoop(ForLoopTree node, Void v) { reportError("no.for.loop", node); return super.visitForLoop(node, v); } @Override public Void visitMethod(MethodTree node, Void v) { boolean oldInsideMethod = insideMethod; insideMethod = true; try { Name name = node.getName(); if (name.contentEquals("")) { return super.visitMethod(node, v); } else { checkSampling(node); if (isExitHandler(node)) { if (node.getParameters().size() != 1 || !"int".equals(node.getParameters().get(0).getType().toString())) { reportError("onexit.invalid", node); return super.visitMethod(node, v); } } if (isErrorHandler(node)) { Element thrElement = getElement(node.getParameters().get(0).getType()); if (node.getParameters().size() != 1 || !THROWABLE_TYPE.equals(thrElement.toString())) { reportError("onerror.invalid", node); } } for (VariableTree vt : node.getParameters()) { vt.accept( new TreeScanner() { @Override public Void visitAnnotation(AnnotationTree at, Void p) { isInAnnotation = true; try { return super.visitAnnotation(at, p); } finally { isInAnnotation = false; } } }, null); } Set flags = node.getModifiers().getFlags(); if (shortSyntax) { if (isStatic(flags)) { reportError("no.static.method", node); } if (isSynchronized(flags)) { reportError("no.synchronized.methods", node); } } else { boolean isStatic = isStatic(flags); if (isStatic) { boolean isPublic = isPublic(node.getModifiers().getFlags()); if (isPublic) { if (isSynchronized(flags)) { reportError("no.synchronized.methods", node); } } else { // force the "public" modifier only on the annotated methods if (isAnnotated(node)) { reportError("method.should.be.public", node); } } } else { reportError("no.instance.method", node); } } node.accept(jfrFieldNameCollector, null); return super.visitMethod(node, v); } } finally { insideMethod = oldInsideMethod; } } private void addEventFieldNames(AnnotationTree at) { for (ExpressionTree et1 : at.getArguments()) { addEventFieldName((AssignmentTree) et1); } } private void addEventFieldName(AssignmentTree assignmentTree) { String varName = assignmentTree.getVariable().toString(); if (varName.equals("name")) { eventFieldNames.add(assignmentTree.getExpression().toString()); } } @Override public Void visitNewArray(NewArrayTree node, Void v) { if (!isInAnnotation) { reportError("no.array.creation", node); } return super.visitNewArray(node, v); } @Override public Void visitNewClass(NewClassTree node, Void v) { Element e = getElement(node); TypeElement te = (TypeElement) e.getEnclosingElement(); reportError("no.new.object", node); return super.visitNewClass(node, v); } @Override public Void visitReturn(ReturnTree node, Void v) { if (node.getExpression() != null) { TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), node); while (tp != null) { tp = tp.getParentPath(); Tree leaf = tp.getLeaf(); if (leaf.getKind() == Tree.Kind.METHOD) { if (isAnnotated((MethodTree) leaf)) { reportError("return.type.should.be.void", node); } else { return super.visitReturn(node, v); } } } } return super.visitReturn(node, v); } @Override public Void visitMemberSelect(MemberSelectTree node, Void v) { if (!isInAnnotation) { if (node.getIdentifier().contentEquals("class")) { TypeMirror tm = getType(node.getExpression()); String typeName = tm != null ? tm.toString() : ""; // Allow class literals only for @Injected service types if (!injectedServiceTypes.contains(typeName)) { reportError("no.class.literals", node); } } } return super.visitMemberSelect(node, v); } @Override public Void visitAnnotation(AnnotationTree node, Void unused) { try { isInAnnotation = true; return super.visitAnnotation(node, unused); } finally { isInAnnotation = false; } } @Override public Void visitSynchronized(SynchronizedTree node, Void v) { reportError("no.synchronized.blocks", node); return super.visitSynchronized(node, v); } @Override public Void visitThrow(ThrowTree node, Void v) { reportError("no.throw", node); return super.visitThrow(node, v); } @Override public Void visitTry(TryTree node, Void v) { reportError("no.try", node); return super.visitTry(node, v); } @Override public Void visitVariable(VariableTree vt, Void p) { VariableElement ve = (VariableElement) getElement(vt); if (ve.getEnclosingElement().getKind() == ElementKind.CLASS) { // only applying to fields Injected injected = ve.getAnnotation(Injected.class); if (injected != null) { // Track the injected service/interface type and its package for later method-call checks String serviceTypeName = ve.asType().toString(); injectedServiceTypes.add(serviceTypeName); int lastDot = serviceTypeName.lastIndexOf('.'); if (lastDot > 0) { String servicePackage = serviceTypeName.substring(0, lastDot); servicePackages.add(servicePackage); } // Validate that the injected service type is declared by some extension if (!isDeclaredExtensionService(serviceTypeName)) { reportError("invalid.injected.service", vt); } if (vt.getInitializer() != null) { reportError("injected.no.initializer", vt.getInitializer()); } // Best effort: if the field type is itself an Extension subtype, collect permissions if (extensionTm != null && verifier.getTypeUtils().isSubtype(ve.asType(), extensionTm)) { collectExtensionPermissions(ve.asType()); } } else { // JFR field name collection still applies for non-injected fields vt.accept(jfrFieldNameCollector, null); } } return super.visitVariable(vt, p); } /** * Returns true if the given service class name is declared by any extension. * * Compile-time validation notes: * - This check operates without loading classes. It inspects extension metadata * (BTrace-Extension-Services in MANIFEST.MF and legacy META-INF/btrace-extension.properties) * to verify that an @Injected service type is declared by some extension. * - It complements the bytecode-time check in instr (BTraceProbeNode) and the runtime * reflection-based validation in the agent (Client#validateDeclaredServices). The latter * ensures correctness under the actual runtime classloader/JPMS environment. */ private boolean isDeclaredExtensionService(String serviceClassName) { String resourceName = serviceClassName.replace('.', '/') + ".class"; ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = getClass().getClassLoader(); } try { URL res = cl != null ? cl.getResource(resourceName) : null; if (res != null && "jar".equals(res.getProtocol())) { String spec = res.getFile(); int idx = spec.indexOf('!'); if (idx > 0) { String jarUrl = spec.substring(0, idx); if (jarUrl.startsWith("file:")) { jarUrl = jarUrl.substring(5); } String jarPath = URLDecoder.decode(jarUrl, StandardCharsets.UTF_8.name()); if (declaresServiceInJar(jarPath, serviceClassName)) { return true; } } } // Fallback: scan system classpath jars String cp = System.getProperty("java.class.path", ""); String[] parts = cp.split(java.io.File.pathSeparator); for (String p : parts) { if (p.endsWith(".jar")) { try { if (declaresServiceInJar(p, serviceClassName)) { return true; } } catch (Exception ignored) { } } } } catch (Exception ignored) { } return false; } private boolean declaresServiceInJar(String jarPath, String serviceClassName) { try (JarFile jf = new JarFile(jarPath)) { Manifest mf = jf.getManifest(); if (mf != null) { Attributes attrs = mf.getMainAttributes(); String services = attrs.getValue("BTrace-Extension-Services"); if (services != null && !services.trim().isEmpty()) { for (String s : services.split("[,\\s]+")) { if (serviceClassName.equals(s.trim())) { return true; } } } } java.util.zip.ZipEntry props = jf.getEntry("META-INF/btrace-extension.properties"); if (props != null) { Properties pr = new Properties(); try (java.io.InputStream is = jf.getInputStream(props)) { pr.load(is); } String services = pr.getProperty("services"); if (services != null && !services.trim().isEmpty()) { for (String s : services.split("[,\\s]+")) { if (serviceClassName.equals(s.trim())) { return true; } } } } } catch (Exception ignored) { } return false; } private void processEventFields(AssignmentTree t) { if (t.getExpression() instanceof AnnotationTree) { AnnotationTree at = (AnnotationTree) t.getExpression(); addEventFieldNames(at); } else if (t.getExpression() instanceof NewArrayTree) { for (ExpressionTree et2 : ((NewArrayTree) t.getExpression()).getInitializers()) { AnnotationTree at = (AnnotationTree) et2; addEventFieldNames(at); } } } @Override public Void visitWhileLoop(WhileLoopTree node, Void v) { reportError("no.while.loop", node); return super.visitWhileLoop(node, v); } @Override public Void visitOther(Tree node, Void v) { reportError("no.other", node); return super.visitOther(node, v); } private boolean isStatic(Set modifiers) { for (Modifier m : modifiers) { if (m == Modifier.STATIC) { return true; } } return false; } private boolean isSynchronized(Set modifiers) { for (Modifier m : modifiers) { if (m == Modifier.SYNCHRONIZED) { return true; } } return false; } private boolean isPublic(Set modifiers) { for (Modifier m : modifiers) { if (m == Modifier.PUBLIC) { return true; } } return false; } private boolean isErrorHandler(MethodTree node) { ModifiersTree mt = node.getModifiers(); List annos = mt.getAnnotations(); for (AnnotationTree at : annos) { String annFqn = verifier.annotationName(at); if (ON_ERROR_TYPE.equals(annFqn)) { return true; } } return false; } private boolean isExitHandler(MethodTree node) { ModifiersTree mt = node.getModifiers(); List annos = mt.getAnnotations(); for (AnnotationTree at : annos) { String annFqn = verifier.annotationName(at); if (ON_EXIT_TYPE.equals(annFqn)) { return true; } } return false; } private boolean isAnnotated(MethodTree node) { ModifiersTree mt = node.getModifiers(); List annos = mt.getAnnotations(); for (AnnotationTree at : annos) { String annFqn = verifier.annotationName(at); if (annFqn != null && annFqn.startsWith("org.openjdk.btrace.core.annotations")) { return true; } } return false; } private void checkSampling(MethodTree node) { ExecutableElement ee = (ExecutableElement) getElement(node); Sampled s = ee.getAnnotation(Sampled.class); OnMethod om = ee.getAnnotation(OnMethod.class); if (s != null && om != null) { Kind k = om.location().value(); switch (k) { case ENTRY: case RETURN: case ERROR: case CALL: { return; } default: { // noop } } reportError("sampler.invalid.location", node); } } private void checkLValue(Tree variable) { if (variable.getKind() == Tree.Kind.ARRAY_ACCESS) { reportError("no.assignment", variable); return; } if (variable.getKind() != Tree.Kind.IDENTIFIER) { if (className != null) { String name = variable.toString(); name = name.substring(0, name.lastIndexOf('.')); if (!className.equals(name)) { reportError("no.assignment", variable); } } else { reportError("no.assignment", variable); } } } private void reportError(String msg, Tree node) { SourcePositions srcPos = verifier.getSourcePositions(); CompilationUnitTree compUnit = verifier.getCompilationUnit(); if (compUnit != null) { long pos = srcPos.getStartPosition(compUnit, node); long line = compUnit.getLineMap().getLineNumber(pos); String name = compUnit.getSourceFile().getName(); Element e = getElement(node); msg = String.format("%s:%d:%s [%s]", name, line, Messages.get(msg), e); verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e); } else { verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, msg); } } private Element getElement(Tree t) { TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), t); Element e = verifier.getTreeUtils().getElement(tp); if (e == null) { if (t.getKind() == Tree.Kind.NEW_CLASS) { e = verifier .getTreeUtils() .getElement(new TreePath(tp, ((NewClassTree) t).getIdentifier())); } if (t.getKind() == Tree.Kind.THROW) { e = verifier.getTreeUtils().getElement(new TreePath(tp, ((ThrowTree) t).getExpression())); } if (e == null) { verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, t.toString()); } } return e; } private TypeMirror getType(Tree t) { TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), t); return verifier.getTreeUtils().getTypeMirror(tp); } // Collect extension permissions from manifest (canonical). Legacy annotations removed. private void collectExtensionPermissions(TypeMirror extensionType) { Element extensionElement = verifier.getTypeUtils().asElement(extensionType); if (extensionElement == null) { return; } // Also try to read permissions from the extension API JAR manifest // Attribute: BTrace-Extension-Permissions: CSV of Permission names try { String className = extensionType.toString(); String resourceName = className.replace('.', '/') + ".class"; ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = getClass().getClassLoader(); } URL res = cl != null ? cl.getResource(resourceName) : null; if (res != null && "jar".equals(res.getProtocol())) { String spec = res.getFile(); int idx = spec.indexOf('!'); if (idx > 0) { String jarUrl = spec.substring(0, idx); if (jarUrl.startsWith("file:")) { jarUrl = jarUrl.substring(5); } String jarPath = URLDecoder.decode(jarUrl, StandardCharsets.UTF_8.name()); try (JarFile jf = new JarFile(jarPath)) { Manifest mf = jf.getManifest(); if (mf != null) { Attributes attrs = mf.getMainAttributes(); String perms = attrs.getValue("BTrace-Extension-Permissions"); if (perms != null && !perms.trim().isEmpty()) { String[] parts = perms.split("[,\\s]+"); for (String p : parts) { try { requiredPermissions.add(Permission.valueOf(p.trim())); } catch (IllegalArgumentException ignored) { // ignore unknown entries } } } } } } } } catch (Exception ignored) { // Best-effort only; ignore any IO errors } } // No compile-time check for probe-declared permissions; runtime grants enforce permissions. } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/oneliner/OnelinerAST.java ================================================ package org.openjdk.btrace.compiler.oneliner; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * AST node definitions for BTrace oneliner language. * Represents the parsed structure of a oneliner expression. */ public class OnelinerAST { /** Root node of the oneliner AST */ public static class OnelinerNode { public final ProbeClause probe; public OnelinerNode(ProbeClause probe) { this.probe = Objects.requireNonNull(probe, "probe cannot be null"); } } /** Probe clause: probe_spec [filter] action_block */ public static class ProbeClause { public final ProbeSpec probeSpec; public final Filter filter; // nullable public final ActionBlock actionBlock; public ProbeClause(ProbeSpec probeSpec, Filter filter, ActionBlock actionBlock) { this.probeSpec = Objects.requireNonNull(probeSpec, "probeSpec cannot be null"); this.filter = filter; // nullable this.actionBlock = Objects.requireNonNull(actionBlock, "actionBlock cannot be null"); } } /** Probe specification: class::method @location */ public static class ProbeSpec { public final String classPattern; public final String methodPattern; public final Location location; public ProbeSpec(String classPattern, String methodPattern, Location location) { this.classPattern = Objects.requireNonNull(classPattern, "classPattern cannot be null"); this.methodPattern = Objects.requireNonNull(methodPattern, "methodPattern cannot be null"); this.location = Objects.requireNonNull(location, "location cannot be null"); } } /** Probe location */ public enum Location { ENTRY, RETURN, ERROR } /** Filter: if predicate */ public static class Filter { public final FilterType type; public final Comparator comparator; public final Object value; // Integer for duration ms, String/Integer for arg comparison public Filter(FilterType type, Comparator comparator, Object value) { this.type = Objects.requireNonNull(type, "type cannot be null"); this.comparator = Objects.requireNonNull(comparator, "comparator cannot be null"); this.value = Objects.requireNonNull(value, "value cannot be null"); } } /** Filter type */ public enum FilterType { DURATION, // duration>100ms ARG_COMPARE // args[0]=="value" } /** Comparison operator */ public enum Comparator { GT, LT, EQ, GTE, LTE, NEQ } /** Action block: { action, action, ... } */ public static class ActionBlock { public final List actions; public ActionBlock(List actions) { this.actions = new ArrayList<>(Objects.requireNonNull(actions, "actions cannot be null")); if (this.actions.isEmpty()) { throw new IllegalArgumentException("Action block cannot be empty"); } } } /** Base interface for all actions */ public interface Action {} /** Print action: print [identifier, ...] */ public static class PrintAction implements Action { public final List args; // nullable for bare "print" public PrintAction(List args) { this.args = args != null ? new ArrayList<>(args) : new ArrayList<>(); } } /** Count action: count */ public static class CountAction implements Action { public CountAction() {} } /** Time action: time */ public static class TimeAction implements Action { public TimeAction() {} } /** Stack action: stack [(depth)] */ public static class StackAction implements Action { public final Integer depth; // nullable for default depth public StackAction(Integer depth) { if (depth != null && depth <= 0) { throw new IllegalArgumentException("Stack depth must be positive, got: " + depth); } this.depth = depth; } } /** For arg filters: args[index] */ public static class ArgFilter extends Filter { public final int argIndex; public ArgFilter(int argIndex, Comparator comparator, Object value) { super(FilterType.ARG_COMPARE, comparator, value); if (argIndex < 0) { throw new IllegalArgumentException("Argument index must be non-negative, got: " + argIndex); } this.argIndex = argIndex; } } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/oneliner/OnelinerCodeGenerator.java ================================================ package org.openjdk.btrace.compiler.oneliner; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.*; /** Generates BTrace Java source code from oneliner AST */ public class OnelinerCodeGenerator { private static final String INDENT = " "; private static final String DEFAULT_CLASS_NAME = "BTraceOneliner"; /** * Generate Java source code from a oneliner AST using the default class name. * * @param node The parsed oneliner AST * @return Generated BTrace Java source code */ public static String generate(OnelinerNode node) { return generate(node, DEFAULT_CLASS_NAME); } /** * Generate Java source code from a oneliner AST with a custom class name. * * @param node The parsed oneliner AST * @param className The class name to use for the generated BTrace script * @return Generated BTrace Java source code */ public static String generate(OnelinerNode node, String className) { if (className == null || className.isEmpty()) { className = DEFAULT_CLASS_NAME; } StringBuilder sb = new StringBuilder(); // Package and imports sb.append("package org.openjdk.btrace.generated;\n\n"); sb.append("import java.util.concurrent.atomic.AtomicInteger;\n"); sb.append("import org.openjdk.btrace.core.BTraceUtils;\n"); sb.append("import org.openjdk.btrace.core.annotations.*;\n"); sb.append("import org.openjdk.btrace.core.types.AnyType;\n\n"); // Class declaration sb.append("@BTrace\n"); sb.append("public class ").append(className).append(" {\n\n"); ProbeClause probe = node.probe; // Generate fields (for count action) boolean hasCountAction = hasCountAction(probe.actionBlock); if (hasCountAction) { sb.append(INDENT) .append("private static final AtomicInteger counter = BTraceUtils.newAtomicInteger(0);\n\n"); } // Generate probe method generateProbeMethod(sb, probe); // Generate @OnEvent method (for count action) if (hasCountAction) { sb.append("\n"); sb.append(INDENT).append("@OnEvent\n"); sb.append(INDENT).append("public static void onEvent() {\n"); sb.append(INDENT).append(INDENT); sb.append("BTraceUtils.println(\"count: \" + BTraceUtils.get(counter));\n"); sb.append(INDENT).append("}\n"); } // Close class sb.append("}\n"); return sb.toString(); } private static void generateProbeMethod(StringBuilder sb, ProbeClause probe) { ProbeSpec spec = probe.probeSpec; // Generate @OnMethod annotation sb.append(INDENT).append("@OnMethod("); sb.append("clazz=\"").append(escapeJavaString(spec.classPattern)).append("\", "); sb.append("method=\"").append(escapeJavaString(spec.methodPattern)).append("\""); // Add location if not ENTRY if (spec.location != Location.ENTRY) { sb.append(",\n"); sb.append(INDENT).append(INDENT).append(INDENT); sb.append("location=@Location(Kind.").append(spec.location.name()).append(")"); } sb.append(")\n"); // Generate method signature sb.append(INDENT).append("public static void probe("); List params = collectParameters(probe); for (int i = 0; i < params.size(); i++) { if (i > 0) { sb.append(", "); } sb.append(params.get(i)); } sb.append(") {\n"); // Generate method body generateMethodBody(sb, probe); // Close method sb.append(INDENT).append("}\n"); } private static List collectParameters(ProbeClause probe) { List params = new ArrayList<>(); Set seen = new HashSet<>(); // Collect parameters from filter if (probe.filter != null) { addFilterParameters(params, seen, probe.filter, probe.probeSpec.location); } // Collect parameters from actions for (Action action : probe.actionBlock.actions) { if (action instanceof PrintAction) { addPrintParameters(params, seen, (PrintAction) action, probe.probeSpec.location); } else if (action instanceof TimeAction) { addParameter(params, seen, "@Duration long duration"); } } return params; } private static void addFilterParameters( List params, Set seen, Filter filter, Location location) { if (filter.type == FilterType.DURATION) { addParameter(params, seen, "@Duration long duration"); } else if (filter.type == FilterType.ARG_COMPARE) { addParameter(params, seen, "AnyType[] args"); } } private static void addPrintParameters( List params, Set seen, PrintAction action, Location location) { for (String arg : action.args) { switch (arg) { case "method": addParameter(params, seen, "@ProbeMethodName String method"); break; case "class": addParameter(params, seen, "@ProbeClassName String className"); break; case "args": addParameter(params, seen, "AnyType[] args"); break; case "duration": addParameter(params, seen, "@Duration long duration"); break; case "return": addParameter(params, seen, "@Return AnyType returnValue"); break; case "self": addParameter(params, seen, "@Self Object self"); break; } } } private static void addParameter(List params, Set seen, String param) { if (!seen.contains(param)) { params.add(param); seen.add(param); } } private static void generateMethodBody(StringBuilder sb, ProbeClause probe) { String indent = INDENT + INDENT; // Generate filter if-statement if (probe.filter != null) { sb.append(indent).append("if ("); generateFilterCondition(sb, probe.filter); sb.append(") {\n"); indent = INDENT + INDENT + INDENT; } // Generate actions for (Action action : probe.actionBlock.actions) { if (action instanceof PrintAction) { generatePrintAction(sb, indent, (PrintAction) action); } else if (action instanceof CountAction) { sb.append(indent).append("BTraceUtils.incrementAndGet(counter);\n"); } else if (action instanceof TimeAction) { generateTimeAction(sb, indent); } else if (action instanceof StackAction) { generateStackAction(sb, indent, (StackAction) action); } } // Close filter if-statement if (probe.filter != null) { sb.append(INDENT).append(INDENT).append("}\n"); } } private static void generateFilterCondition(StringBuilder sb, Filter filter) { if (filter.type == FilterType.DURATION) { sb.append("duration "); sb.append(comparatorToJava(filter.comparator)); sb.append(" "); int millis = (Integer) filter.value; long nanos = millis * 1_000_000L; sb.append(nanos).append("L"); } else if (filter.type == FilterType.ARG_COMPARE) { ArgFilter argFilter = (ArgFilter) filter; sb.append("args.length > ").append(argFilter.argIndex); sb.append(" && "); if (argFilter.value == null) { sb.append("args[").append(argFilter.argIndex).append("] "); sb.append(comparatorToJava(argFilter.comparator)); sb.append(" null"); } else if (argFilter.value instanceof String) { if (argFilter.comparator == Comparator.EQ) { sb.append("BTraceUtils.compare(\"") .append(escapeJavaString((String) argFilter.value)) .append("\", "); sb.append("BTraceUtils.str(args[").append(argFilter.argIndex).append("]))"); } else if (argFilter.comparator == Comparator.NEQ) { sb.append("!BTraceUtils.compare(\"") .append(escapeJavaString((String) argFilter.value)) .append("\", "); sb.append("BTraceUtils.str(args[").append(argFilter.argIndex).append("]))"); } else { throw new IllegalArgumentException( "String comparison only supports == and !=, got: " + argFilter.comparator); } } else if (argFilter.value instanceof Integer) { sb.append("((Number)args[").append(argFilter.argIndex).append("]).longValue() "); sb.append(comparatorToJava(argFilter.comparator)); sb.append(" ").append(argFilter.value).append("L"); } } } private static String comparatorToJava(Comparator comp) { switch (comp) { case GT: return ">"; case LT: return "<"; case EQ: return "=="; case GTE: return ">="; case LTE: return "<="; case NEQ: return "!="; default: throw new IllegalArgumentException("Unknown comparator: " + comp); } } private static void generatePrintAction(StringBuilder sb, String indent, PrintAction action) { if (action.args.isEmpty()) { // Bare "print" with no arguments sb.append(indent).append("BTraceUtils.println(\"trace\");\n"); } else { sb.append(indent).append("BTraceUtils.println("); for (int i = 0; i < action.args.size(); i++) { if (i > 0) { sb.append(" + \" \" + "); } String arg = action.args.get(i); switch (arg) { case "method": sb.append("method"); break; case "class": sb.append("className"); break; case "args": sb.append("BTraceUtils.str(args)"); break; case "duration": sb.append("duration"); break; case "return": sb.append("BTraceUtils.str(returnValue)"); break; case "self": sb.append("BTraceUtils.str(self)"); break; } } sb.append(");\n"); } } private static void generateTimeAction(StringBuilder sb, String indent) { sb.append(indent) .append("BTraceUtils.println(\"execution time: \" + (duration / 1000000) + \" ms\");\n"); } private static void generateStackAction(StringBuilder sb, String indent, StackAction action) { sb.append(indent).append("BTraceUtils.jstack("); if (action.depth != null) { sb.append(action.depth); } sb.append(");\n"); } private static boolean hasCountAction(ActionBlock actionBlock) { for (Action action : actionBlock.actions) { if (action instanceof CountAction) { return true; } } return false; } private static String escapeJavaString(String str) { return str.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t"); } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/oneliner/OnelinerException.java ================================================ package org.openjdk.btrace.compiler.oneliner; /** * Exception thrown when parsing or validating BTrace oneliner expressions. * Includes position tracking for better error messages. */ public class OnelinerException extends RuntimeException { private final int position; private final String input; public OnelinerException(String message, String input, int position) { super(formatMessage(message, input, position)); this.position = position; this.input = input; } public OnelinerException(String message, String input, int position, Throwable cause) { super(formatMessage(message, input, position), cause); this.position = position; this.input = input; } public int getPosition() { return position; } public String getInput() { return input; } private static String formatMessage(String message, String input, int position) { StringBuilder sb = new StringBuilder(); sb.append("Oneliner syntax error at position ").append(position).append(":\n"); sb.append(input).append("\n"); if (position >= 0 && position < input.length()) { for (int i = 0; i < position; i++) { sb.append(" "); } sb.append("^\n"); } sb.append(message); return sb.toString(); } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/oneliner/OnelinerLexer.java ================================================ package org.openjdk.btrace.compiler.oneliner; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** Tokenizer for BTrace oneliner language */ public class OnelinerLexer { /** Token types */ public enum TokenType { // Literals IDENTIFIER, NUMBER, STRING, REGEX, // Keywords IF, PRINT, COUNT, STACK, TIME, ENTRY, RETURN, ERROR, // Special identifiers METHOD, ARGS, DURATION, RETURN_VAL, SELF, CLASS, // Symbols DOUBLE_COLON, AT, LBRACE, RBRACE, LPAREN, RPAREN, LBRACKET, RBRACKET, COMMA, EQ, GT, LT, GTE, LTE, NEQ, EOF } /** Token with type, value, and position */ public static class Token { public final TokenType type; public final String value; public final int position; public Token(TokenType type, String value, int position) { this.type = type; this.value = value; this.position = position; } @Override public String toString() { return type + (value != null && !value.isEmpty() ? "(" + value + ")" : "") + "@" + position; } } private static final Map KEYWORDS = new HashMap<>(); static { KEYWORDS.put("if", TokenType.IF); KEYWORDS.put("print", TokenType.PRINT); KEYWORDS.put("count", TokenType.COUNT); KEYWORDS.put("stack", TokenType.STACK); KEYWORDS.put("time", TokenType.TIME); KEYWORDS.put("entry", TokenType.ENTRY); KEYWORDS.put("return", TokenType.RETURN_VAL); KEYWORDS.put("error", TokenType.ERROR); KEYWORDS.put("method", TokenType.METHOD); KEYWORDS.put("args", TokenType.ARGS); KEYWORDS.put("duration", TokenType.DURATION); KEYWORDS.put("self", TokenType.SELF); KEYWORDS.put("class", TokenType.CLASS); } private final String input; private int pos = 0; public OnelinerLexer(String input) { this.input = input != null ? input : ""; } /** Tokenize the entire input */ public List tokenize() { List tokens = new ArrayList<>(); Token token; while ((token = nextToken()).type != TokenType.EOF) { tokens.add(token); } tokens.add(token); // add EOF return tokens; } /** Get the next token */ public Token nextToken() { skipWhitespace(); if (isEOF()) { return new Token(TokenType.EOF, "", pos); } int startPos = pos; char ch = peek(); // Two-character operators if (ch == ':' && peek(1) == ':') { pos += 2; return new Token(TokenType.DOUBLE_COLON, "::", startPos); } if (ch == '=' && peek(1) == '=') { pos += 2; return new Token(TokenType.EQ, "==", startPos); } if (ch == '>' && peek(1) == '=') { pos += 2; return new Token(TokenType.GTE, ">=", startPos); } if (ch == '<' && peek(1) == '=') { pos += 2; return new Token(TokenType.LTE, "<=", startPos); } if (ch == '!' && peek(1) == '=') { pos += 2; return new Token(TokenType.NEQ, "!=", startPos); } // Single-character tokens switch (ch) { case '@': pos++; return new Token(TokenType.AT, "@", startPos); case '{': pos++; return new Token(TokenType.LBRACE, "{", startPos); case '}': pos++; return new Token(TokenType.RBRACE, "}", startPos); case '(': pos++; return new Token(TokenType.LPAREN, "(", startPos); case ')': pos++; return new Token(TokenType.RPAREN, ")", startPos); case '[': pos++; return new Token(TokenType.LBRACKET, "[", startPos); case ']': pos++; return new Token(TokenType.RBRACKET, "]", startPos); case ',': pos++; return new Token(TokenType.COMMA, ",", startPos); case '>': pos++; return new Token(TokenType.GT, ">", startPos); case '<': pos++; return new Token(TokenType.LT, "<", startPos); } // Regex pattern /pattern/ if (ch == '/') { return scanRegex(startPos); } // String "value" if (ch == '"') { return scanString(startPos); } // Number if (Character.isDigit(ch)) { return scanNumber(startPos); } // Identifier or keyword if (isIdentifierStart(ch)) { return scanIdentifierOrKeyword(startPos); } throw new OnelinerException( "Unexpected character: '" + ch + "'", input, pos); } private Token scanRegex(int startPos) { pos++; // skip initial '/' StringBuilder sb = new StringBuilder(); while (!isEOF() && peek() != '/') { if (peek() == '\\' && peek(1) == '/') { sb.append('/'); pos += 2; } else { sb.append(peek()); pos++; } } if (isEOF()) { throw new OnelinerException( "Unterminated regex pattern, expected closing '/'", input, startPos); } pos++; // skip closing '/' return new Token(TokenType.REGEX, sb.toString(), startPos); } private Token scanString(int startPos) { pos++; // skip initial '"' StringBuilder sb = new StringBuilder(); while (!isEOF() && peek() != '"') { if (peek() == '\\') { pos++; if (isEOF()) { throw new OnelinerException( "Unterminated string, expected closing '\"'", input, startPos); } char escaped = peek(); switch (escaped) { case 'n': sb.append('\n'); break; case 't': sb.append('\t'); break; case 'r': sb.append('\r'); break; case '\\': sb.append('\\'); break; case '"': sb.append('"'); break; default: sb.append(escaped); } pos++; } else { sb.append(peek()); pos++; } } if (isEOF()) { throw new OnelinerException( "Unterminated string, expected closing '\"'", input, startPos); } pos++; // skip closing '"' return new Token(TokenType.STRING, sb.toString(), startPos); } private Token scanNumber(int startPos) { StringBuilder sb = new StringBuilder(); while (!isEOF() && Character.isDigit(peek())) { sb.append(peek()); pos++; } return new Token(TokenType.NUMBER, sb.toString(), startPos); } private Token scanIdentifierOrKeyword(int startPos) { StringBuilder sb = new StringBuilder(); while (!isEOF() && isIdentifierPart(peek())) { sb.append(peek()); pos++; } String value = sb.toString(); TokenType type = KEYWORDS.getOrDefault(value, TokenType.IDENTIFIER); return new Token(type, value, startPos); } private void skipWhitespace() { while (!isEOF() && Character.isWhitespace(peek())) { pos++; } } private boolean isEOF() { return pos >= input.length(); } private char peek() { return peek(0); } private char peek(int offset) { int index = pos + offset; return index < input.length() ? input.charAt(index) : '\0'; } private boolean isIdentifierStart(char ch) { return Character.isLetter(ch) || ch == '_' || ch == '*' || ch == '?'; } private boolean isIdentifierPart(char ch) { return Character.isLetterOrDigit(ch) || ch == '_' || ch == '.' || ch == '*' || ch == '?' || ch == '$'; } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/oneliner/OnelinerParser.java ================================================ package org.openjdk.btrace.compiler.oneliner; import java.util.ArrayList; import java.util.List; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.*; import org.openjdk.btrace.compiler.oneliner.OnelinerLexer.Token; import org.openjdk.btrace.compiler.oneliner.OnelinerLexer.TokenType; /** Parser for BTrace oneliner language */ public class OnelinerParser { private final String input; private final List tokens; private int current = 0; private OnelinerParser(String input, List tokens) { this.input = input; this.tokens = tokens; } /** Parse a oneliner expression */ public static OnelinerNode parse(String input) { OnelinerLexer lexer = new OnelinerLexer(input); List tokens = lexer.tokenize(); OnelinerParser parser = new OnelinerParser(input, tokens); return parser.parseOneliner(); } /** oneliner = probe_spec [filter] action_block */ private OnelinerNode parseOneliner() { ProbeSpec probeSpec = parseProbeSpec(); Filter filter = null; if (check(TokenType.IF)) { filter = parseFilter(); } ActionBlock actionBlock = parseActionBlock(); if (!isAtEnd()) { error("Unexpected token after action block: " + peek().type); } ProbeClause probe = new ProbeClause(probeSpec, filter, actionBlock); return new OnelinerNode(probe); } /** probe_spec = class_pattern :: method_pattern @ location */ private ProbeSpec parseProbeSpec() { String classPattern = parsePattern("class pattern"); if (!match(TokenType.DOUBLE_COLON)) { error("Expected '::' after class pattern"); } String methodPattern = parsePattern("method pattern"); if (!match(TokenType.AT)) { error("Expected '@' before location"); } Location location = parseLocation(); return new ProbeSpec(classPattern, methodPattern, location); } /** Parse class or method pattern (supports wildcards * ? and regex /.../) */ private String parsePattern(String patternName) { if (check(TokenType.REGEX)) { String regex = advance().value; return "/" + regex + "/"; } else if (check(TokenType.IDENTIFIER)) { return advance().value; } else if (check(TokenType.LT)) { // Handle special method names like and advance(); // consume '<' if (!check(TokenType.IDENTIFIER)) { error("Expected 'init' or 'clinit' after '<'"); } String methodName = advance().value; if (!check(TokenType.GT)) { error("Expected '>' after '" + methodName + "'"); } advance(); // consume '>' return "<" + methodName + ">"; } else { error("Expected " + patternName + " (identifier or regex)"); return null; } } /** location = entry | return | error */ private Location parseLocation() { if (match(TokenType.ENTRY)) { return Location.ENTRY; } else if (match(TokenType.RETURN_VAL)) { return Location.RETURN; } else if (match(TokenType.ERROR)) { return Location.ERROR; } else { error("Expected location: entry, return, or error"); return null; } } /** filter = if predicate */ private Filter parseFilter() { if (!match(TokenType.IF)) { error("Expected 'if' keyword"); } return parsePredicate(); } /** predicate = duration_predicate | arg_predicate */ private Filter parsePredicate() { if (check(TokenType.DURATION)) { return parseDurationPredicate(); } else if (check(TokenType.ARGS)) { return parseArgPredicate(); } else { error("Expected 'duration' or 'args' in filter predicate"); return null; } } /** duration_predicate = duration comparator number ms */ private Filter parseDurationPredicate() { if (!match(TokenType.DURATION)) { error("Expected 'duration'"); } Comparator comp = parseComparator(); if (!check(TokenType.NUMBER)) { error("Expected number after comparator in duration filter"); } int millis = Integer.parseInt(advance().value); if (!check(TokenType.IDENTIFIER) || !peek().value.equals("ms")) { error("Expected 'ms' after duration value"); } advance(); // consume 'ms' return new Filter(FilterType.DURATION, comp, millis); } /** arg_predicate = args [ number ] comparator value */ private Filter parseArgPredicate() { if (!match(TokenType.ARGS)) { error("Expected 'args'"); } if (!match(TokenType.LBRACKET)) { error("Expected '[' after 'args'"); } if (!check(TokenType.NUMBER)) { error("Expected number for argument index"); } int argIndex = Integer.parseInt(advance().value); if (!match(TokenType.RBRACKET)) { error("Expected ']' after argument index"); } Comparator comp = parseComparator(); Object value; if (check(TokenType.STRING)) { value = advance().value; } else if (check(TokenType.NUMBER)) { value = Integer.parseInt(advance().value); } else if (check(TokenType.IDENTIFIER) && peek().value.equals("null")) { value = null; advance(); } else { error("Expected string, number, or null in argument comparison"); return null; } return new ArgFilter(argIndex, comp, value); } /** comparator = > | < | == | >= | <= | != */ private Comparator parseComparator() { if (match(TokenType.GT)) { return Comparator.GT; } else if (match(TokenType.LT)) { return Comparator.LT; } else if (match(TokenType.EQ)) { return Comparator.EQ; } else if (match(TokenType.GTE)) { return Comparator.GTE; } else if (match(TokenType.LTE)) { return Comparator.LTE; } else if (match(TokenType.NEQ)) { return Comparator.NEQ; } else { error("Expected comparison operator: >, <, ==, >=, <=, !="); return null; } } /** action_block = { action [, action]* } */ private ActionBlock parseActionBlock() { if (!match(TokenType.LBRACE)) { error("Expected '{' to start action block"); } List actions = new ArrayList<>(); actions.add(parseAction()); while (match(TokenType.COMMA)) { actions.add(parseAction()); } if (!match(TokenType.RBRACE)) { error("Expected '}' to end action block or ',' for another action"); } return new ActionBlock(actions); } /** action = print_action | count_action | time_action | stack_action */ private Action parseAction() { if (check(TokenType.PRINT)) { return parsePrintAction(); } else if (check(TokenType.COUNT)) { return parseCountAction(); } else if (check(TokenType.TIME)) { return parseTimeAction(); } else if (check(TokenType.STACK)) { return parseStackAction(); } else { error("Expected action: print, count, time, or stack"); return null; } } /** print_action = print [identifier [, identifier]*] */ private PrintAction parsePrintAction() { if (!match(TokenType.PRINT)) { error("Expected 'print'"); } List args = new ArrayList<>(); // Check if there are any identifiers to print if (isPrintIdentifier(peek().type)) { args.add(parsePrintIdentifier()); while (match(TokenType.COMMA) && isAtEnd() == false) { // Check if next token after comma is a print identifier or another action if (isPrintIdentifier(peek().type)) { args.add(parsePrintIdentifier()); } else { // Put the comma back by decrementing current current--; break; } } } return new PrintAction(args); } private boolean isPrintIdentifier(TokenType type) { return type == TokenType.METHOD || type == TokenType.ARGS || type == TokenType.DURATION || type == TokenType.RETURN_VAL || type == TokenType.SELF || type == TokenType.CLASS; } private String parsePrintIdentifier() { if (match(TokenType.METHOD)) { return "method"; } else if (match(TokenType.ARGS)) { return "args"; } else if (match(TokenType.DURATION)) { return "duration"; } else if (match(TokenType.RETURN_VAL)) { return "return"; } else if (match(TokenType.SELF)) { return "self"; } else if (match(TokenType.CLASS)) { return "class"; } else { error("Expected print identifier: method, args, duration, return, self, or class"); return null; } } /** count_action = count */ private CountAction parseCountAction() { if (!match(TokenType.COUNT)) { error("Expected 'count'"); } return new CountAction(); } /** time_action = time */ private TimeAction parseTimeAction() { if (!match(TokenType.TIME)) { error("Expected 'time'"); } return new TimeAction(); } /** stack_action = stack [(number)] */ private StackAction parseStackAction() { if (!match(TokenType.STACK)) { error("Expected 'stack'"); } Integer depth = null; if (match(TokenType.LPAREN)) { if (!check(TokenType.NUMBER)) { error("Expected number for stack depth"); } depth = Integer.parseInt(advance().value); if (!match(TokenType.RPAREN)) { error("Expected ')' after stack depth"); } } return new StackAction(depth); } // Helper methods private boolean match(TokenType... types) { for (TokenType type : types) { if (check(type)) { advance(); return true; } } return false; } private boolean check(TokenType type) { if (isAtEnd()) { return false; } return peek().type == type; } private Token advance() { if (!isAtEnd()) { current++; } return previous(); } private boolean isAtEnd() { return peek().type == TokenType.EOF; } private Token peek() { return tokens.get(current); } private Token previous() { return tokens.get(current - 1); } private void error(String message) { int position = isAtEnd() ? input.length() : peek().position; throw new OnelinerException(message, input, position); } } ================================================ FILE: btrace-compiler/src/main/java/org/openjdk/btrace/compiler/oneliner/OnelinerValidator.java ================================================ package org.openjdk.btrace.compiler.oneliner; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.*; /** Semantic validator for BTrace oneliner AST */ public class OnelinerValidator { /** * Validate a parsed oneliner AST * * @throws OnelinerException if validation fails */ public static void validate(OnelinerNode node, String input) { ProbeClause probe = node.probe; // Validate probe spec validateProbeSpec(probe.probeSpec, input); // Validate filter for location if (probe.filter != null) { validateFilter(probe.filter, probe.probeSpec.location, input); } // Validate actions for location validateActions(probe.actionBlock, probe.probeSpec.location, input); } private static void validateProbeSpec(ProbeSpec spec, String input) { // Validate class pattern is not empty if (spec.classPattern == null || spec.classPattern.trim().isEmpty()) { throw new OnelinerException("Class pattern cannot be empty", input, 0); } // Validate method pattern is not empty if (spec.methodPattern == null || spec.methodPattern.trim().isEmpty()) { throw new OnelinerException("Method pattern cannot be empty", input, 0); } // Validate regex patterns validateRegexPattern(spec.classPattern, "class pattern", input); validateRegexPattern(spec.methodPattern, "method pattern", input); } private static void validateRegexPattern(String pattern, String patternName, String input) { if (pattern.startsWith("/") && pattern.endsWith("/")) { String regex = pattern.substring(1, pattern.length() - 1); try { Pattern.compile(regex); } catch (PatternSyntaxException e) { throw new OnelinerException( "Invalid regex in " + patternName + ": " + e.getMessage(), input, 0); } } } private static void validateFilter(Filter filter, Location location, String input) { if (filter.type == FilterType.DURATION) { // Duration filter only valid for RETURN and ERROR locations if (location != Location.RETURN && location != Location.ERROR) { throw new OnelinerException( "'duration' filter requires @return or @error location (found: @" + location.name().toLowerCase() + ")", input, 0); } // Validate duration value is positive if (filter.value instanceof Integer && (Integer) filter.value <= 0) { throw new OnelinerException("Duration value must be positive", input, 0); } } if (filter.type == FilterType.ARG_COMPARE) { ArgFilter argFilter = (ArgFilter) filter; // Argument index validation already done in AST construction if (argFilter.argIndex < 0) { throw new OnelinerException("Argument index must be non-negative", input, 0); } } } private static void validateActions(ActionBlock actionBlock, Location location, String input) { Set seenActions = new HashSet<>(); for (Action action : actionBlock.actions) { if (action instanceof PrintAction) { validatePrintAction((PrintAction) action, location, input); } else if (action instanceof CountAction) { // Count action is valid for all locations if (seenActions.contains("count")) { throw new OnelinerException("Duplicate 'count' action in action block", input, 0); } seenActions.add("count"); } else if (action instanceof TimeAction) { // Time action requires RETURN or ERROR location if (location != Location.RETURN && location != Location.ERROR) { throw new OnelinerException( "'time' action requires @return or @error location (found: @" + location.name().toLowerCase() + ")", input, 0); } if (seenActions.contains("time")) { throw new OnelinerException("Duplicate 'time' action in action block", input, 0); } seenActions.add("time"); } else if (action instanceof StackAction) { StackAction stackAction = (StackAction) action; // Stack depth validation already done in AST construction if (stackAction.depth != null && stackAction.depth <= 0) { throw new OnelinerException( "Stack depth must be positive (got: " + stackAction.depth + ")", input, 0); } } } } private static void validatePrintAction(PrintAction action, Location location, String input) { for (String arg : action.args) { switch (arg) { case "duration": if (location != Location.RETURN && location != Location.ERROR) { throw new OnelinerException( "'duration' identifier only available with @return or @error location (found: @" + location.name().toLowerCase() + ")", input, 0); } break; case "return": if (location != Location.RETURN) { throw new OnelinerException( "'return' identifier only available with @return location (found: @" + location.name().toLowerCase() + ")", input, 0); } break; case "method": case "args": case "self": case "class": // These are valid for all locations break; default: throw new OnelinerException("Unknown print identifier: '" + arg + "'", input, 0); } } } } ================================================ FILE: btrace-compiler/src/test/java/org/openjdk/btrace/compiler/JfrEventsTest.java ================================================ package org.openjdk.btrace.compiler; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.util.Map; import org.junit.jupiter.api.Test; import org.objectweb.asm.ClassReader; import org.objectweb.asm.util.CheckClassAdapter; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.instr.BTraceProbe; import org.openjdk.btrace.instr.BTraceProbeFactory; import static org.junit.jupiter.api.Assertions.fail; public class JfrEventsTest { @Test public void testCompile() throws Exception { URL input = JfrEventsTest.class.getResource("/JfrEventsProbe.java"); File inputFile = new File(input.toURI()); Map data = new Compiler(true) .compile( inputFile, new PrintWriter(System.err), null, System.getProperty("java.class.path")); BTraceProbeFactory factory = new BTraceProbeFactory(SharedSettings.GLOBAL); for (byte[] bytes : data.values()) { BTraceProbe probe = factory.createProbe(bytes); verifyCode(probe.getFullBytecode()); verifyCode(probe.getDataHolderBytecode()); } } private void verifyCode(byte[] code) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); CheckClassAdapter.verify(new ClassReader(code), true, pw); if (sw.toString().contains("AnalyzerException")) { System.err.println(sw); fail(); } } } ================================================ FILE: btrace-compiler/src/test/java/org/openjdk/btrace/compiler/TypeErasureTest.java ================================================ package org.openjdk.btrace.compiler; import org.junit.jupiter.api.Test; import org.objectweb.asm.ClassReader; import org.objectweb.asm.util.CheckClassAdapter; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.instr.BTraceProbe; import org.openjdk.btrace.instr.BTraceProbeFactory; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.util.Map; import static org.junit.jupiter.api.Assertions.fail; public class TypeErasureTest { @Test void testTypeErasure() throws Exception { URL input = TypeErasureTest.class.getResource("/HistoProbe.java"); File inputFile = new File(input.toURI()); Map data = new Compiler(true) .compile( inputFile, new PrintWriter(System.err), null, System.getProperty("java.class.path")); BTraceProbeFactory factory = new BTraceProbeFactory(SharedSettings.GLOBAL); for (byte[] bytes : data.values()) { BTraceProbe probe = factory.createProbe(bytes); verifyCode(probe.getFullBytecode()); verifyCode(probe.getDataHolderBytecode()); } } private void verifyCode(byte[] code) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); CheckClassAdapter.verify(new ClassReader(code), true, pw); if (sw.toString().contains("AnalyzerException")) { System.err.println(sw); fail(); } } } ================================================ FILE: btrace-compiler/src/test/java/org/openjdk/btrace/compiler/oneliner/OnelinerCodeGeneratorTest.java ================================================ package org.openjdk.btrace.compiler.oneliner; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.OnelinerNode; class OnelinerCodeGeneratorTest { @Test void testGenerateSimpleEntry() { String input = "javax.swing.JButton::setText @entry { print method }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("@BTrace")); assertTrue(generated.contains("@OnMethod(clazz=\"javax.swing.JButton\", method=\"setText\")")); assertTrue(generated.contains("@ProbeMethodName String method")); assertTrue(generated.contains("BTraceUtils.println(method)")); } @Test void testGenerateReturn() { String input = "java.util.HashMap::get @return { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("@Location(Kind.RETURN)")); } @Test void testGenerateError() { String input = "java.lang.OutOfMemoryError:: @error { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("@Location(Kind.ERROR)")); assertTrue(generated.contains("clazz=\"java.lang.OutOfMemoryError\"")); assertTrue(generated.contains("method=\"\"")); } @Test void testGeneratePrintMultipleArgs() { String input = "Test::test @entry { print method, args }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("@ProbeMethodName String method")); assertTrue(generated.contains("AnyType[] args")); assertTrue(generated.contains("BTraceUtils.println(method + \" \" + BTraceUtils.str(args))")); } @Test void testGeneratePrintAllIdentifiers() { String input = "Test::test @return { print method, class, args, duration, return, self }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("@ProbeMethodName String method")); assertTrue(generated.contains("@ProbeClassName String className")); assertTrue(generated.contains("AnyType[] args")); assertTrue(generated.contains("@Duration long duration")); assertTrue(generated.contains("@Return AnyType returnValue")); assertTrue(generated.contains("@Self Object self")); } @Test void testGenerateCount() { String input = "java.util.HashMap::get @entry { count }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("private static final AtomicInteger counter")); assertTrue(generated.contains("BTraceUtils.incrementAndGet(counter)")); assertTrue(generated.contains("@OnEvent")); assertTrue(generated.contains("BTraceUtils.println(\"count: \" + BTraceUtils.get(counter))")); } @Test void testGenerateTime() { String input = "Test::test @return { time }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("@Duration long duration")); assertTrue(generated.contains("execution time")); } @Test void testGenerateStackWithoutDepth() { String input = "Test::test @entry { stack }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("BTraceUtils.jstack()")); } @Test void testGenerateStackWithDepth() { String input = "Test::test @entry { stack(10) }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("BTraceUtils.jstack(10)")); } @Test void testGenerateDurationFilterGreaterThan() { String input = "java.sql.Statement::execute @return if duration>100ms { print method }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("if (duration > 100000000L)")); assertTrue(generated.contains("BTraceUtils.println(method)")); } @Test void testGenerateDurationFilterGreaterThanOrEqual() { String input = "Test::test @return if duration>=50ms { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("if (duration >= 50000000L)")); } @Test void testGenerateDurationFilterLessThan() { String input = "Test::test @return if duration<10ms { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("if (duration < 10000000L)")); } @Test void testGenerateArgFilterStringEquals() { String input = "Test::test @entry if args[0]==\"test\" { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("args.length > 0")); assertTrue( generated.contains("BTraceUtils.compare(\"test\", BTraceUtils.str(args[0]))")); } @Test void testGenerateArgFilterStringNotEquals() { String input = "Test::test @entry if args[0]!=\"foo\" { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue( generated.contains("!BTraceUtils.compare(\"foo\", BTraceUtils.str(args[0]))")); } @Test void testGenerateArgFilterNumberGreaterThan() { String input = "Test::test @entry if args[1]>100 { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("args.length > 1")); assertTrue(generated.contains("((Number)args[1]).longValue() > 100L")); } @Test void testGenerateMultipleActions() { String input = "Test::test @entry { print method, count, stack }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("BTraceUtils.println(method)")); assertTrue(generated.contains("BTraceUtils.incrementAndGet(counter)")); assertTrue(generated.contains("BTraceUtils.jstack()")); assertTrue(generated.contains("@OnEvent")); } @Test void testGenerateRegexPattern() { String input = "/com\\.myapp\\..*/::execute @entry { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("clazz=\"/com\\\\.myapp\\\\..*/")); // Escaping in generated code } @Test void testClassNameIsUnique() throws InterruptedException { String input = "Test::test @entry { print }"; OnelinerNode ast1 = OnelinerParser.parse(input); String generated1 = OnelinerCodeGenerator.generate(ast1); OnelinerNode ast2 = OnelinerParser.parse(input); String generated2 = OnelinerCodeGenerator.generate(ast2); // Class name is fixed (BTraceOneliner) - uniqueness not required since // each oneliner gets its own temp file and class loader context assertTrue(generated1.contains("class BTraceOneliner")); assertTrue(generated2.contains("class BTraceOneliner")); } @Test void testGeneratedCodeHasCorrectPackage() { String input = "Test::test @entry { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("package org.openjdk.btrace.generated;")); } @Test void testGeneratedCodeHasRequiredImports() { String input = "Test::test @entry { print }"; OnelinerNode ast = OnelinerParser.parse(input); String generated = OnelinerCodeGenerator.generate(ast); assertTrue(generated.contains("import org.openjdk.btrace.core.BTraceUtils;")); assertTrue(generated.contains("import org.openjdk.btrace.core.annotations.*;")); assertTrue(generated.contains("import org.openjdk.btrace.core.types.AnyType;")); } } ================================================ FILE: btrace-compiler/src/test/java/org/openjdk/btrace/compiler/oneliner/OnelinerIntegrationTest.java ================================================ package org.openjdk.btrace.compiler.oneliner; import static org.junit.jupiter.api.Assertions.*; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import org.junit.jupiter.api.Test; import org.openjdk.btrace.compiler.Compiler; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.OnelinerNode; /** * Integration tests that verify oneliner-generated code compiles with the real BTrace compiler. */ class OnelinerIntegrationTest { @Test void testCompileSimpleEntry() { String oneliner = "javax.swing.JButton::setText @entry { print method }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileReturnWithFilter() { String oneliner = "java.sql.Statement::execute @return if duration>100ms { print method, duration }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileCount() { String oneliner = "java.util.HashMap::get @entry { count }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileStack() { String oneliner = "java.lang.OutOfMemoryError:: @return { stack(10) }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileMultipleActions() { String oneliner = "Test::test @entry { print method, count, stack }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileArgFilter() { String oneliner = "Test::test @entry if args[0]==\"test\" { print }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileRegexPattern() { String oneliner = "/com\\.myapp\\..*/::execute @entry { print }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileError() { String oneliner = "java.lang.Exception:: @error { print self }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompileTime() { String oneliner = "Test::test @return { time }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } @Test void testCompilePrintAllIdentifiers() { String oneliner = "Test::test @return { print method, class, args, duration, return, self }"; byte[] bytecode = compileOneliner(oneliner); assertNotNull(bytecode); assertTrue(bytecode.length > 0); } /** * Helper method to compile a oneliner end-to-end: parse -> validate -> generate -> compile */ private byte[] compileOneliner(String oneliner) { try { // Parse OnelinerNode ast = OnelinerParser.parse(oneliner); // Validate OnelinerValidator.validate(ast, oneliner); // Generate Java source String javaSource = OnelinerCodeGenerator.generate(ast); // Print generated source for debugging (if test fails) if (System.getProperty("btrace.test.debug") != null) { System.out.println("=== Generated Java for: " + oneliner + " ==="); System.out.println(javaSource); System.out.println("=== End Generated Java ===\n"); } // Compile with real BTrace compiler Compiler compiler = new Compiler(); StringWriter errorWriter = new StringWriter(); Map compiled = compiler.compile( "BTraceOneliner.java", javaSource, new PrintWriter(errorWriter), ".", System.getProperty("java.class.path")); // Check for compilation errors String errors = errorWriter.toString(); if (!errors.isEmpty()) { fail("Compilation failed for oneliner: " + oneliner + "\nErrors:\n" + errors); } assertNotNull(compiled, "Compiler returned null"); assertFalse(compiled.isEmpty(), "No classes were compiled"); // Return the first compiled class return compiled.values().iterator().next(); } catch (Exception e) { fail("Failed to compile oneliner: " + oneliner + "\nException: " + e.getMessage(), e); return null; } } } ================================================ FILE: btrace-compiler/src/test/java/org/openjdk/btrace/compiler/oneliner/OnelinerParserTest.java ================================================ package org.openjdk.btrace.compiler.oneliner; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.openjdk.btrace.compiler.oneliner.OnelinerAST.*; class OnelinerParserTest { @Test void testSimpleEntryProbe() { String input = "javax.swing.*::setText @entry { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertNotNull(ast); assertEquals("javax.swing.*", ast.probe.probeSpec.classPattern); assertEquals("setText", ast.probe.probeSpec.methodPattern); assertEquals(Location.ENTRY, ast.probe.probeSpec.location); assertNull(ast.probe.filter); assertEquals(1, ast.probe.actionBlock.actions.size()); assertTrue(ast.probe.actionBlock.actions.get(0) instanceof PrintAction); } @Test void testReturnProbe() { String input = "java.util.HashMap::get @return { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertEquals(Location.RETURN, ast.probe.probeSpec.location); } @Test void testErrorProbe() { String input = "java.lang.OutOfMemoryError:: @error { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertEquals("java.lang.OutOfMemoryError", ast.probe.probeSpec.classPattern); assertEquals("", ast.probe.probeSpec.methodPattern); assertEquals(Location.ERROR, ast.probe.probeSpec.location); } @Test void testRegexPattern() { String input = "/com\\.myapp\\..*/::execute @entry { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertEquals("/com\\.myapp\\..*/", ast.probe.probeSpec.classPattern); } @Test void testMethodRegexPattern() { String input = "java.sql.Statement::/execute.*/ @entry { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertEquals("java.sql.Statement", ast.probe.probeSpec.classPattern); assertEquals("/execute.*/", ast.probe.probeSpec.methodPattern); } @Test void testPrintWithArguments() { String input = "javax.swing.*::* @entry { print method, args }"; OnelinerNode ast = OnelinerParser.parse(input); PrintAction printAction = (PrintAction) ast.probe.actionBlock.actions.get(0); assertEquals(2, printAction.args.size()); assertEquals("method", printAction.args.get(0)); assertEquals("args", printAction.args.get(1)); } @Test void testPrintAllIdentifiers() { String input = "Test::test @return { print method, args, duration, return, self, class }"; OnelinerNode ast = OnelinerParser.parse(input); PrintAction printAction = (PrintAction) ast.probe.actionBlock.actions.get(0); assertEquals(6, printAction.args.size()); assertEquals("method", printAction.args.get(0)); assertEquals("args", printAction.args.get(1)); assertEquals("duration", printAction.args.get(2)); assertEquals("return", printAction.args.get(3)); assertEquals("self", printAction.args.get(4)); assertEquals("class", printAction.args.get(5)); } @Test void testCountAction() { String input = "java.util.HashMap::get @entry { count }"; OnelinerNode ast = OnelinerParser.parse(input); assertTrue(ast.probe.actionBlock.actions.get(0) instanceof CountAction); } @Test void testTimeAction() { String input = "java.sql.Statement::execute @return { time }"; OnelinerNode ast = OnelinerParser.parse(input); assertTrue(ast.probe.actionBlock.actions.get(0) instanceof TimeAction); } @Test void testStackAction() { String input = "Test::test @entry { stack }"; OnelinerNode ast = OnelinerParser.parse(input); StackAction stackAction = (StackAction) ast.probe.actionBlock.actions.get(0); assertNull(stackAction.depth); } @Test void testStackActionWithDepth() { String input = "Test::test @entry { stack(10) }"; OnelinerNode ast = OnelinerParser.parse(input); StackAction stackAction = (StackAction) ast.probe.actionBlock.actions.get(0); assertEquals(10, stackAction.depth); } @Test void testMultipleActions() { String input = "Test::test @entry { print method, count, stack }"; OnelinerNode ast = OnelinerParser.parse(input); assertEquals(3, ast.probe.actionBlock.actions.size()); assertTrue(ast.probe.actionBlock.actions.get(0) instanceof PrintAction); assertTrue(ast.probe.actionBlock.actions.get(1) instanceof CountAction); assertTrue(ast.probe.actionBlock.actions.get(2) instanceof StackAction); } @Test void testDurationFilterGreaterThan() { String input = "java.sql.Statement::execute @return if duration>100ms { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertNotNull(ast.probe.filter); assertEquals(FilterType.DURATION, ast.probe.filter.type); assertEquals(Comparator.GT, ast.probe.filter.comparator); assertEquals(100, ast.probe.filter.value); } @Test void testDurationFilterGreaterThanOrEqual() { String input = "Test::test @return if duration>=50ms { print }"; OnelinerNode ast = OnelinerParser.parse(input); assertEquals(Comparator.GTE, ast.probe.filter.comparator); assertEquals(50, ast.probe.filter.value); } @Test void testArgFilterStringEquals() { String input = "Test::test @entry if args[0]==\"test\" { print }"; OnelinerNode ast = OnelinerParser.parse(input); ArgFilter argFilter = (ArgFilter) ast.probe.filter; assertEquals(FilterType.ARG_COMPARE, argFilter.type); assertEquals(0, argFilter.argIndex); assertEquals(Comparator.EQ, argFilter.comparator); assertEquals("test", argFilter.value); } @Test void testArgFilterNumberGreaterThan() { String input = "Test::test @entry if args[1]>100 { print }"; OnelinerNode ast = OnelinerParser.parse(input); ArgFilter argFilter = (ArgFilter) ast.probe.filter; assertEquals(1, argFilter.argIndex); assertEquals(Comparator.GT, argFilter.comparator); assertEquals(100, argFilter.value); } @Test void testArgFilterNotEquals() { String input = "Test::test @entry if args[0]!=\"foo\" { print }"; OnelinerNode ast = OnelinerParser.parse(input); ArgFilter argFilter = (ArgFilter) ast.probe.filter; assertEquals(Comparator.NEQ, argFilter.comparator); } @Test void testErrorMissingDoubleColon() { String input = "javax.swing.* setText @entry { print }"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } @Test void testErrorMissingAt() { String input = "javax.swing.*::setText entry { print }"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } @Test void testErrorMissingActionBlock() { String input = "javax.swing.*::setText @entry"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } @Test void testErrorMissingClosingBrace() { String input = "javax.swing.*::setText @entry { print"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } @Test void testErrorInvalidLocation() { String input = "javax.swing.*::setText @invalid { print }"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } @Test void testErrorInvalidAction() { String input = "javax.swing.*::setText @entry { invalid }"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } @Test void testErrorEmptyActionBlock() { String input = "javax.swing.*::setText @entry { }"; assertThrows(OnelinerException.class, () -> OnelinerParser.parse(input)); } } ================================================ FILE: btrace-compiler/src/test/resources/HistoProbe.java ================================================ package test; import static org.openjdk.btrace.core.BTraceUtils.*; import org.openjdk.btrace.core.annotations.*; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @BTrace public class HistoProbe { private static Map histo = newHashMap(); @OnMethod(clazz = "javax.swing.JComponent", method = "") public static void onMethod(@Self Object obj) { String cn = name(classOf(obj)); AtomicInteger ai = get((Map)histo, cn); if (ai == null) { ai = newAtomicInteger(1); put(histo, cn, ai); } else { incrementAndGet(ai); // WORKS if commented out } } @OnTimer(1000) public static void print() { printNumberMap("Component Histogram", histo); } } ================================================ FILE: btrace-compiler/src/test/resources/JfrEventsProbe.java ================================================ package test; import org.openjdk.btrace.core.BTraceUtils; import org.openjdk.btrace.core.annotations.*; import org.openjdk.btrace.core.jfr.JfrEvent; import static org.openjdk.btrace.core.BTraceUtils.*; import static org.openjdk.btrace.core.BTraceUtils.Jfr.*; @BTrace public class JfrEventsProbe { @Event( name = "CustomEvent", label = "Custom Event", fields = { @Event.Field(type = Event.FieldType.INT, name = "a"), @Event.Field(type = Event.FieldType.STRING, name = "b") } ) private static JfrEvent.Factory customEventFactory; @OnMethod(clazz = "/.*/", method = "/.*/") public static void onMethod() { JfrEvent event = prepareEvent(customEventFactory); setEventField(event, "a", 10); setEventField(event, "b", "hello"); commit(event); } @PeriodicEvent(name = "PeriodicEvent", fields = @Event.Field(type = Event.FieldType.INT, name = "cnt", kind = @Event.Field.Kind(name = Event.FieldKind.TIMESTAMP)), period = "1 s") public static void onPeriod(JfrEvent event) { if (shouldCommit(event)) { setEventField(event, "cnt", 1); commit(event); } } } ================================================ FILE: btrace-compiler/src/test/resources/MetricsExtensionProbe.java ================================================ package resources; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Duration; import org.openjdk.btrace.core.annotations.Injected; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.metrics.MetricsService; import org.openjdk.btrace.metrics.histogram.HistogramConfig; import org.openjdk.btrace.metrics.histogram.HistogramMetric; import org.openjdk.btrace.metrics.histogram.HistogramSnapshot; import org.openjdk.btrace.metrics.stats.StatsMetric; import org.openjdk.btrace.metrics.stats.StatsSnapshot; import static org.openjdk.btrace.core.BTraceUtils.println; /** * Test probe that uses the metrics extension to verify that: * 1. @Injected service fields are recognized * 2. Methods on service return types (HistogramMetric, StatsMetric) are allowed * 3. Methods on snapshot objects (HistogramSnapshot, StatsSnapshot) are allowed */ @BTrace public class MetricsExtensionProbe { @Injected private static MetricsService metrics; private static HistogramMetric histogram; private static StatsMetric stats; @OnMethod(clazz = "java.lang.String", method = "length", location = @Location(Kind.RETURN)) public static void onStringLength(@Duration long durationNanos) { // Initialize metrics on first call if (histogram == null) { histogram = metrics.histogramMicros("string.length"); stats = metrics.stats("string.length.stats"); } // Record duration - these calls should be allowed by the verifier long durationMicros = durationNanos / 1000; histogram.record(durationMicros); stats.record(durationMicros); // Get snapshots - these calls should be allowed HistogramSnapshot h = histogram.snapshot(); StatsSnapshot s = stats.snapshot(); // Call methods on snapshots - these should also be allowed println("Count: " + s.count()); println("Mean: " + s.mean()); println("P99: " + h.p99()); } } ================================================ FILE: btrace-core/JMH_BENCHMARKS.md ================================================ # JMH Benchmarks for Binary Protocol v2 ## Overview This document describes how to run the JMH (Java Microbenchmark Harness) benchmarks for the binary protocol v2 implementation. The benchmarks compare the performance of the new binary protocol against Java serialization across multiple command types and data sizes. ## Running Benchmarks ### Run All Benchmarks ```bash ./gradlew :btrace-core:jmh ``` This will run all benchmarks with default settings: - 3 warmup iterations - 5 measurement iterations - 2 forks - 1 thread - Both throughput and average time modes ### Filter Benchmarks Use the `jmhInclude` property to filter which benchmarks to run: ```bash # Run only serialization benchmarks ./gradlew :btrace-core:jmh -PjmhInclude=".*Serialize" # Run only deserialization benchmarks ./gradlew :btrace-core:jmh -PjmhInclude=".*Deserialize" # Run only round-trip benchmarks ./gradlew :btrace-core:jmh -PjmhInclude=".*RoundTrip" # Run benchmarks for specific command type ./gradlew :btrace-core:jmh -PjmhInclude=".*InstrumentCommand.*" # Run benchmarks for specific data size ./gradlew :btrace-core:jmh -PjmhInclude=".*small.*" ``` ## Benchmark Structure ### Command Types Tested The benchmarks test the following command types: 1. **InstrumentCommand** - Bytecode instrumentation with byte arrays 2. **MessageCommand** - Text messages 3. **ErrorCommand** - Error reporting 4. **EventCommand** - Event notifications 5. **StatusCommand** - Status updates 6. **NumberMapDataCommand** - Maps with numeric values 7. **StringMapDataCommand** - Maps with string values 8. **NumberDataCommand** - Single numeric values 9. **GridDataCommand** - Tabular data 10. **ExitCommand** - Exit signals ### Data Sizes Each command type is tested with three data sizes: - **small** - Minimal data (e.g., 50 chars, 100 bytes, 5 entries) - **medium** - Moderate data (e.g., 500 chars, 1KB, 50 entries) - **large** - Large data (e.g., 5000 chars, 10KB, 500 entries) ### Benchmark Methods Each command type/size combination is tested with: - `binarySerialize` - Serialize using binary protocol - `binaryDeserialize` - Deserialize using binary protocol - `javaSerialize` - Serialize using Java serialization - `javaDeserialize` - Deserialize using Java serialization - `binaryRoundTrip` - Full round-trip with binary protocol - `javaRoundTrip` - Full round-trip with Java serialization ## Interpreting Results ### Throughput (ops/us) Higher is better. Shows how many operations per microsecond. ### Average Time (us/op) Lower is better. Shows microseconds per operation. ### Example Output ``` Benchmark (commandType) (dataSize) Mode Cnt Score Error Units BinaryProtocolBenchmark.binarySerialize MessageCommand small avgt 10 5.234 ± 0.123 us/op BinaryProtocolBenchmark.javaSerialize MessageCommand small avgt 10 12.456 ± 0.234 us/op ``` This shows binary serialization is ~2.4x faster than Java serialization for small messages. ## Expected Performance Improvements Based on architecture design goals, the binary protocol should deliver: | Metric | Target Improvement | |--------|-------------------| | Serialization Speed | 3-6x faster | | Deserialization Speed | 3-6x faster | | Wire Size | 2-5x smaller | | Memory Allocation | Significantly reduced | With compression enabled (for messages >1KB): - Wire size improvements can be 10-100x for highly compressible data - Speed may be slower due to compression overhead ## Customizing Benchmarks Edit `btrace-core/build.gradle` to change JMH settings: ```gradle jmh { warmupIterations = 3 // Number of warmup iterations iterations = 5 // Number of measurement iterations fork = 2 // Number of forked JVM processes threads = 1 // Number of worker threads timeUnit = 'ms' // Time unit for results benchmarkMode = ['avgt', 'thrpt'] // Benchmark modes } ``` ## Benchmark Output Location JMH results are written to: - Console output during execution - `btrace-core/build/reports/jmh/` (if configured) ## Best Practices 1. **Close other applications** - Minimize system noise 2. **Run multiple times** - JMH forks handle this automatically 3. **Check for GC** - Look for GC pauses in output 4. **Compare same system** - Run before/after on same hardware 5. **Use realistic data** - Adjust data sizes to match production ## Advanced Usage ### Custom JMH Options Pass JMH options directly: ```bash ./gradlew :btrace-core:jmh -PjmhInclude=".*Serialize" \ --args="-prof gc -prof stack -rf json -rff results.json" ``` Common profilers: - `-prof gc` - GC profiling - `-prof stack` - Stack traces - `-prof perfnorm` - Normalized perf events ### Output Formats - `-rf csv` - CSV output - `-rf json` - JSON output - `-rf text` - Text output (default) ## Troubleshooting ### Out of Memory Increase heap size: ```bash ./gradlew :btrace-core:jmh -Dorg.gradle.jvmargs="-Xmx4g" ``` ### Benchmarks Take Too Long Reduce iterations: ```gradle jmh { warmupIterations = 1 iterations = 2 fork = 1 } ``` ### Need More Detail Enable JMH verbose output: ```bash ./gradlew :btrace-core:jmh --args="-v EXTRA" ``` ================================================ FILE: btrace-core/build.gradle ================================================ import org.apache.tools.ant.filters.* buildscript { scriptHandler -> apply from: rootProject.file('buildSrc/shared.gradle'), to: scriptHandler } plugins { alias(libs.plugins.jmh) id 'jacoco' } dependencies { implementation libs.slf4j implementation libs.slf4j.simple implementation libs.jctools implementation libs.asm implementation libs.asm.util jmh libs.jmh jmh libs.jmh.annprocess } jmh { warmupIterations = 3 iterations = 5 fork = 2 threads = 1 timeUnit = 'ms' def jmhIncludeProp = project.findProperty('jmhInclude') includes = jmhIncludeProp ? [jmhIncludeProp] : ['.*BinaryProtocol.*'] benchmarkMode = ['avgt', 'thrpt'] } processResources { filter ReplaceTokens, tokens: [ "btrace.version": project.version, "hash" : getGitCommit() ] } jacoco { toolVersion = "0.8.14" } jacocoTestReport { dependsOn test reports { xml.required = true html.required = true csv.required = false } // Focus on v2 protocol package afterEvaluate { classDirectories.setFrom(files(classDirectories.files.collect { fileTree(dir: it, include: [ '**/org/openjdk/btrace/core/comm/**/*.class' ]) })) } } test { finalizedBy jacocoTestReport } ================================================ FILE: btrace-core/src/jmh/java/org/openjdk/btrace/core/comm/v2/BinaryProtocolBenchmark.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.ErrorCommand; import org.openjdk.btrace.core.comm.EventCommand; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.GridDataCommand; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.openjdk.btrace.core.comm.NumberDataCommand; import org.openjdk.btrace.core.comm.NumberMapDataCommand; import org.openjdk.btrace.core.comm.StatusCommand; import org.openjdk.btrace.core.comm.StringMapDataCommand; import org.openjdk.btrace.core.comm.WireIO; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; /** * JMH benchmarks for the binary protocol vs Java serialization. * Tests serialization and deserialization performance across all command types. */ @BenchmarkMode({Mode.Throughput, Mode.AverageTime}) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(2) @State(Scope.Benchmark) public class BinaryProtocolBenchmark { @Param({"InstrumentCommand", "MessageCommand", "ErrorCommand", "EventCommand", "StatusCommand", "NumberMapDataCommand", "StringMapDataCommand", "NumberDataCommand", "GridDataCommand", "ExitCommand"}) private String commandType; @Param({"small", "medium", "large"}) private String dataSize; private BinaryCommand binaryCommand; private Command javaCommand; @Setup public void setup() { switch (commandType) { case "InstrumentCommand": byte[] code = createByteArray(dataSize); String[] args = {"arg1", "arg2", "arg3"}; binaryCommand = new BinaryInstrumentCommand(code, args); javaCommand = new InstrumentCommand(code, args); break; case "MessageCommand": String message = createString(dataSize); binaryCommand = new BinaryMessageCommand(message, false); javaCommand = new MessageCommand(message); break; case "ErrorCommand": Throwable cause = new RuntimeException(createString(dataSize)); binaryCommand = new BinaryErrorCommand(cause.getClass().getName(), cause.getMessage(), null); javaCommand = new ErrorCommand(cause); break; case "EventCommand": String eventName = "test.event." + createString(dataSize); binaryCommand = new BinaryEventCommand(eventName); javaCommand = new EventCommand(eventName); break; case "StatusCommand": binaryCommand = new BinaryStatusCommand(1, true); javaCommand = new StatusCommand(1); break; case "NumberMapDataCommand": Map numberMap = createNumberMap(dataSize); binaryCommand = new BinaryNumberMapDataCommand("test", numberMap); javaCommand = new NumberMapDataCommand("test", numberMap); break; case "StringMapDataCommand": Map stringMap = createStringMap(dataSize); binaryCommand = new BinaryStringMapDataCommand("test", stringMap); javaCommand = new StringMapDataCommand("test", stringMap); break; case "NumberDataCommand": binaryCommand = new BinaryNumberDataCommand("test", 42); javaCommand = new NumberDataCommand("test", 42); break; case "GridDataCommand": List gridData = createGridData(dataSize); List columnNames = new ArrayList<>(); columnNames.add("Name"); columnNames.add("ID"); columnNames.add("Value"); binaryCommand = new BinaryGridDataCommand("test", columnNames, gridData); javaCommand = new GridDataCommand("test", gridData); break; case "ExitCommand": binaryCommand = new BinaryExitCommand(0); javaCommand = new ExitCommand(0); break; default: throw new IllegalArgumentException("Unknown command type: " + commandType); } } @Benchmark public byte[] binarySerialize() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryWireIO.write(baos, binaryCommand); return baos.toByteArray(); } @Benchmark public BinaryCommand binaryDeserialize() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryWireIO.write(baos, binaryCommand); byte[] data = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(data); return BinaryWireIO.read(bais); } @Benchmark public byte[] javaSerialize() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); WireIO.write(oos, javaCommand); oos.close(); return baos.toByteArray(); } @Benchmark public Command javaDeserialize() throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); WireIO.write(oos, javaCommand); oos.close(); byte[] data = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bais); Command cmd = WireIO.read(ois); ois.close(); return cmd; } @Benchmark public byte[] binaryRoundTrip() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryWireIO.write(baos, binaryCommand); byte[] serialized = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(serialized); BinaryCommand deserialized = BinaryWireIO.read(bais); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); BinaryWireIO.write(baos2, deserialized); return baos2.toByteArray(); } @Benchmark public byte[] javaRoundTrip() throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); WireIO.write(oos, javaCommand); oos.close(); byte[] serialized = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bais); Command deserialized = WireIO.read(ois); ois.close(); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); ObjectOutputStream oos2 = new ObjectOutputStream(baos2); WireIO.write(oos2, deserialized); oos2.close(); return baos2.toByteArray(); } private byte[] createByteArray(String size) { int length; switch (size) { case "small": length = 100; break; case "medium": length = 1000; break; case "large": length = 10000; break; default: length = 100; } byte[] data = new byte[length]; for (int i = 0; i < length; i++) { data[i] = (byte) (i % 256); } return data; } private String createString(String size) { int length; switch (size) { case "small": length = 50; break; case "medium": length = 500; break; case "large": length = 5000; break; default: length = 50; } StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append((char) ('A' + (i % 26))); } return sb.toString(); } private Map createNumberMap(String size) { int entries; switch (size) { case "small": entries = 5; break; case "medium": entries = 50; break; case "large": entries = 500; break; default: entries = 5; } Map map = new HashMap<>(); for (int i = 0; i < entries; i++) { map.put("key" + i, i * 1.5); } return map; } private Map createStringMap(String size) { int entries; switch (size) { case "small": entries = 5; break; case "medium": entries = 50; break; case "large": entries = 500; break; default: entries = 5; } Map map = new HashMap<>(); for (int i = 0; i < entries; i++) { map.put("key" + i, "value" + i); } return map; } private List createGridData(String size) { int rows; switch (size) { case "small": rows = 5; break; case "medium": rows = 50; break; case "large": rows = 500; break; default: rows = 5; } List data = new ArrayList<>(rows); for (int i = 0; i < rows; i++) { Object[] row = new Object[3]; row[0] = "row" + i; row[1] = i; row[2] = i * 1.5; data.add(row); } return data; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/Args.java ================================================ package org.openjdk.btrace.core; public final class Args { public static final String SYSTEM_CLASS_PATH = "systemClassPath"; public static final String BOOT_CLASS_PATH = "bootClassPath"; public static final String AGENT_JAR = "agentJar"; public static final String CONFIG = "config"; public static final String SCRIPT = "script"; public static final String SCRIPT_DIR = "scriptdir"; public static final String STARTUP_RETRANSFORM = "startupRetransform"; public static final String DUMP_DIR = "dumpDir"; public static final String DUMP_CLASSES = "dumpClasses"; public static final String CMD_QUEUE_LIMIT = "cmdQueueLimit"; public static final String TRACK_RETRANSFORMS = "trackRetransforms"; public static final String SCRIPT_OUTPUT_FILE = "scriptOutputFile"; public static final String SCRIPT_OUTPUT_DIR = "scriptOutputDir"; public static final String FILE_ROLL_MILLISECONDS = "fileRollMilliseconds"; public static final String FILE_ROLL_MAX_ROLLS = "fileRollMaxRolls"; public static final String TRUSTED = "trusted"; public static final String STATSD = "statsd"; public static final String PROBE_DESC_PATH = "probeDescPath"; public static final String DEBUG = "debug"; public static final String PORT = "port"; public static final String STDOUT = "stdout"; public static final String NO_SERVER = "noServer"; public static final String HELP = "help"; public static final String LIBS = "libs"; public static final String GRANT = "grant"; public static final String DENY = "deny"; public static final String GRANT_ALL = "grantAll"; public static final String ALLOW_EXTENSIONS = "allowExtensions"; public static final String DENY_EXTENSIONS = "denyExtensions"; public static final String ALLOW_PRIVILEGED = "allowPrivileged"; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/ArgsMap.java ================================================ /* * Copyright (c) 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.core; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** A simple argument map wrapper allowing indexed access */ public final class ArgsMap implements Iterable> { private final LinkedHashMap map; public ArgsMap(Map args) { map = args != null ? new LinkedHashMap<>(args) : new LinkedHashMap<>(); } public ArgsMap(String[] argLine) { map = new LinkedHashMap<>(); if (argLine != null) { for (String arg : argLine) { String[] kv = arg.split("="); if (kv.length != 2) { map.put(arg, ""); } else { map.put(kv[0], kv[1]); } } } } public ArgsMap() { this((Map) null); } public ArgsMap(int initialCapacity) { map = new LinkedHashMap<>(initialCapacity); } public static ArgsMap merge(ArgsMap... maps) { Map propMap = new LinkedHashMap<>(); for (ArgsMap map : maps) { propMap.putAll(map.map); } return new ArgsMap(propMap); } public String get(String key) { return map.get(key); } public String get(int idx) { if (idx >= 0 && idx < map.size()) { Iterator> argsIterator = map.entrySet().iterator(); for (int i = 0; i < idx; i++) { argsIterator.next(); } Map.Entry e = argsIterator.next(); return e.getValue() != null ? e.getKey() + "=" + e.getValue() : e.getKey(); } else { return null; } } public void clear() { map.clear(); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public String put(String key, String value) { return map.put(key, value); } @Override public Iterator> iterator() { return map.entrySet().iterator(); } public boolean containsKey(String key) { return map.containsKey(key); } @Override public boolean equals(Object o) { return map.equals(o); } @Override public int hashCode() { return map.hashCode(); } @Override public String toString() { return "ArgsMap{" + "map=" + map + '}'; } public String template(String value) { if (value == null) { return null; } if (value.isEmpty()) { return value; } Matcher matcher = PatternSingleton.INSTANCE.matcher(value); StringBuffer buffer = new StringBuffer(value.length()); while (matcher.find()) { String val = get(matcher.group(1)); matcher.appendReplacement(buffer, val != null ? val : "$0"); } matcher.appendTail(buffer); return buffer.toString(); } private static final class PatternSingleton { // lazy initialization trick // do not compile the pattern until it is actually requested private static final Pattern INSTANCE = Pattern.compile("\\$\\{(.*?)}"); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/BTraceRuntime.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import com.sun.management.HotSpotDiagnosticMXBean; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.instrument.Instrumentation; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.LockInfo; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.lang.management.MonitorInfo; import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.openjdk.btrace.core.jfr.JfrEvent; import org.openjdk.btrace.core.types.AnyType; import org.openjdk.btrace.core.types.BTraceCollection; import org.openjdk.btrace.core.types.BTraceDeque; import org.openjdk.btrace.core.types.BTraceMap; import sun.misc.Unsafe; @SuppressWarnings("deprecation") public final class BTraceRuntime { public static final String CMD_QUEUE_LIMIT_KEY = "org.openjdk.btrace.core.cmdQueueLimit"; private static final boolean messageTimestamp = false; private static final String LINE_SEPARATOR; // perf counter variability - we always variable variability private static final int V_Variable = 3; // perf counter units private static final int V_None = 1; private static final int V_String = 5; private static final int PERF_STRING_LIMIT = 256; // the number of stack frames taking a thread dump adds private static final int THRD_DUMP_FRAMES = 1; // we need Unsafe to load BTrace class bytes as // bootstrap class private static volatile Unsafe unsafe = null; private static Properties dotWriterProps; // this field is 'back-set' by the agent at startup thus needs to remain non-final private static volatile BTraceRuntimeAccessor rtAccessor = () -> null; private static final String INDENT = " "; public static volatile Instrumentation instrumentation = null; static { LINE_SEPARATOR = System.getProperty("line.separator"); } private BTraceRuntime() {} private static Impl getRt() { Impl rt = rtAccessor.getRt(); return rt; } public static long parseLong(String value, long deflt) { if (value == null) { return deflt; } try { return Long.parseLong(value); } catch (NumberFormatException e) { return deflt; } } public static int parseInt(String value, int deflt) { if (value == null) { return deflt; } try { return Integer.parseInt(value); } catch (NumberFormatException e) { return deflt; } } // The following constants are copied from VM code // for jvmstat. public static Unsafe initUnsafe() { try { if (unsafe == null) { unsafe = Unsafe.getUnsafe(); } } catch (SecurityException e) { System.err.println("BTrace warning: unable to initialize Unsafe. BTrace will not function properly"); } return unsafe; } static int getInstrumentationLevel() { return getRt().getInstrumentationLevel(); } static void setInstrumentationLevel(int level) { getRt().setInstrumentationLevel(level); } public static boolean enter() { BTraceRuntime.Impl rt = getRt(); return rt == null || rt.enter(); } /** * Leave method is called by every probed method just before the probe actions end (and actual * probed method continues). */ public static void leave() { BTraceRuntime.Impl rt = getRt(); if (rt != null) { rt.leave(); } } /** Handles exception from BTrace probe actions. */ public static void handleException(Throwable th) { getRt().handleException(th); } // package-private interface to BTraceUtils class. private static String identityStr(Object obj) { int hashCode = java.lang.System.identityHashCode(obj); return obj.getClass().getName() + "@" + Integer.toHexString(hashCode); } static int speculation() { return getRt().speculation(); } static void speculate(int id) { getRt().speculate(id); } static void discard(int id) { getRt().discard(id); } static void commit(int id) { getRt().commit(id); } /** * Indicates whether two given objects are "equal to" one another. For bootstrap classes, returns * the result of calling Object.equals() override. For non-bootstrap classes, the reference * identity comparison is done. * * @param obj1 first object to compare equality * @param obj2 second object to compare equality * @return true if the given objects are equal; false otherwise. */ static boolean compare(Object obj1, Object obj2) { if (obj1 instanceof String) { return obj1.equals(obj2); } else if (obj1.getClass().getClassLoader() == null) { if (obj2 == null || obj2.getClass().getClassLoader() == null) { return obj1.equals(obj2); } // else fall through.. } return obj1 == obj2; } // BTrace map functions static Map newHashMap() { return new BTraceMap<>(new HashMap<>()); } static Map newWeakMap() { return new BTraceMap<>(new WeakHashMap<>()); } static Deque newDeque() { return new BTraceDeque<>(new ArrayDeque<>()); } static Appendable newStringBuilder(boolean threadSafe) { return threadSafe ? new StringBuffer() : new StringBuilder(); } static Appendable newStringBuilder() { return newStringBuilder(false); } static int size(Collection coll) { if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { return coll.size(); } else { throw new IllegalArgumentException(); } } public static boolean isEmpty(Collection coll) { if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { return coll.isEmpty(); } else { throw new IllegalArgumentException(); } } static boolean contains(Collection coll, Object obj) { if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { for (E e : coll) { if (compare(e, obj)) { return true; } } return false; } else { throw new IllegalArgumentException(); } } static Object[] toArray(Collection collection) { if (collection == null) { return new Object[0]; } else { return collection.toArray(); } } static V get(Map map, K key) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.get(key); } else { throw new IllegalArgumentException(); } } static boolean containsKey(Map map, K key) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.containsKey(key); } else { throw new IllegalArgumentException(); } } static boolean containsValue(Map map, V value) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.containsValue(value); } else { throw new IllegalArgumentException(); } } static V put(Map map, K key, V value) { if (map instanceof BTraceMap) { return map.put(key, value); } else { throw new IllegalArgumentException("not a btrace map"); } } static V remove(Map map, K key) { if (map instanceof BTraceMap) { return map.remove(key); } else { throw new IllegalArgumentException("not a btrace map"); } } static void clear(Map map) { if (map instanceof BTraceMap) { map.clear(); } else { throw new IllegalArgumentException("not a btrace map"); } } static int size(Map map) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.size(); } else { throw new IllegalArgumentException(); } } static boolean isEmpty(Map map) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.isEmpty(); } else { throw new IllegalArgumentException(); } } static void putAll(Map src, Map dst) { dst.putAll(src); } static void copy(Map src, Map dst) { dst.clear(); dst.putAll(src); } @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") static void printMap(Map map) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { synchronized (map) { Map m = new HashMap<>(); @SuppressWarnings("unchecked") Set> entries = map.entrySet(); for (Map.Entry e : entries) { m.put(BTraceUtils.Strings.str(e.getKey()), BTraceUtils.Strings.str(e.getValue())); } printStringMap(null, m); } } else { print(BTraceUtils.Strings.str(map)); } } public static void push(Deque queue, V value) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { queue.push(value); } else { throw new IllegalArgumentException(); } } public static void addLast(Deque queue, V value) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { queue.addLast(value); } else { throw new IllegalArgumentException(); } } public static V peekFirst(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.peekFirst(); } else { throw new IllegalArgumentException(); } } public static V peekLast(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.peekLast(); } else { throw new IllegalArgumentException(); } } public static V removeLast(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.removeLast(); } else { throw new IllegalArgumentException(); } } public static V removeFirst(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.removeFirst(); } else { throw new IllegalArgumentException(); } } public static V poll(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.poll(); } else { throw new IllegalArgumentException(); } } public static V peek(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.peek(); } else { throw new IllegalArgumentException(); } } public static void clear(Deque queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { queue.clear(); } else { throw new IllegalArgumentException(); } } public static Appendable append(Appendable buffer, String strToAppend) { try { if (buffer != null && strToAppend != null) { return buffer.append(strToAppend); } else { throw new IllegalArgumentException(); } } catch (IOException e) { throw new IllegalArgumentException(e); } } public static int length(Appendable buffer) { if (buffer instanceof CharSequence) { return ((CharSequence) buffer).length(); } else { throw new IllegalArgumentException(); } } static void printNumber(String name, Number value) { getRt().sendNumberData(name, value); } static void printNumberMap(String name, Map data) { getRt().sendNumberMapData(name, data); } static void printStringMap(String name, Map data) { getRt().sendStringMapData(name, data); } // BTrace exit built-in function static void exit(int exitCode) { getRt().exit(exitCode); } static long sizeof(Object obj) { return getRt().sizeof(obj); } // BTrace command line argument functions static int $length() { return getRt().$length(); } static String $(int n) { return getRt().$(n); } static String $(String key) { return getRt().$(key); } /** * @see BTraceUtils#instanceOf(java.lang.Object, java.lang.String) */ static boolean instanceOf(Object obj, String className) { if (obj instanceof AnyType) { // the only time we can have AnyType on stack // is if it was passed in as a placeholder // for void @Return parameter value if (className.equalsIgnoreCase("void")) { return obj.equals(AnyType.VOID); } return false; } Class objClass = obj.getClass(); ClassLoader cl = objClass.getClassLoader(); cl = cl != null ? cl : ClassLoader.getSystemClassLoader(); try { Class target = cl.loadClass(className); return target.isAssignableFrom(objClass); } catch (ClassNotFoundException e) { // non-existing class return false; } } static AtomicInteger newAtomicInteger(int initVal) { return new BTraceAtomicInteger(initVal); } static int get(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger || ai.getClass().getClassLoader() == null) { return ai.get(); } else { throw new IllegalArgumentException(); } } static void set(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { ai.set(i); } else { throw new IllegalArgumentException(); } } static void lazySet(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { ai.lazySet(i); } else { throw new IllegalArgumentException(); } } static boolean compareAndSet(AtomicInteger ai, int i, int j) { if (ai instanceof BTraceAtomicInteger) { return ai.compareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static boolean weakCompareAndSet(AtomicInteger ai, int i, int j) { if (ai instanceof BTraceAtomicInteger) { return ai.weakCompareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static int getAndIncrement(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndIncrement(); } else { throw new IllegalArgumentException(); } } static int getAndDecrement(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndDecrement(); } else { throw new IllegalArgumentException(); } } static int incrementAndGet(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.incrementAndGet(); } else { throw new IllegalArgumentException(); } } static int decrementAndGet(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.decrementAndGet(); } else { throw new IllegalArgumentException(); } } static int getAndAdd(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndAdd(i); } else { throw new IllegalArgumentException(); } } static int addAndGet(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { return ai.addAndGet(i); } else { throw new IllegalArgumentException(); } } static int getAndSet(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndSet(i); } else { throw new IllegalArgumentException(); } } static AtomicLong newAtomicLong(long initVal) { return new BTraceAtomicLong(initVal); } static long get(AtomicLong al) { if (al instanceof BTraceAtomicLong || al.getClass().getClassLoader() == null) { return al.get(); } else { throw new IllegalArgumentException(); } } static void set(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { al.set(i); } else { throw new IllegalArgumentException(); } } static void lazySet(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { al.lazySet(i); } else { throw new IllegalArgumentException(); } } static boolean compareAndSet(AtomicLong al, long i, long j) { if (al instanceof BTraceAtomicLong) { return al.compareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static boolean weakCompareAndSet(AtomicLong al, long i, long j) { if (al instanceof BTraceAtomicLong) { return al.weakCompareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static long getAndIncrement(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.getAndIncrement(); } else { throw new IllegalArgumentException(); } } static long getAndDecrement(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.getAndDecrement(); } else { throw new IllegalArgumentException(); } } static long incrementAndGet(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.incrementAndGet(); } else { throw new IllegalArgumentException(); } } static long decrementAndGet(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.decrementAndGet(); } else { throw new IllegalArgumentException(); } } static long getAndAdd(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { return al.getAndAdd(i); } else { throw new IllegalArgumentException(); } } static long addAndGet(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { return al.addAndGet(i); } else { throw new IllegalArgumentException(); } } static long getAndSet(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { return al.getAndSet(i); } else { throw new IllegalArgumentException(); } } // BTrace perf counter reading functions static int perfInt(String name) { return getRt().perfInt(name); } static long perfLong(String name) { return getRt().perfLong(name); } static String perfString(String name) { return getRt().perfString(name); } // stack trace functions private static String stackTraceAllStr(int numFrames, boolean printWarning) { Set> traces = Thread.getAllStackTraces().entrySet(); StringBuilder buf = new StringBuilder(); for (Map.Entry t : traces) { buf.append(t.getKey()); buf.append(LINE_SEPARATOR); buf.append(LINE_SEPARATOR); StackTraceElement[] st = t.getValue(); buf.append(stackTraceStr("\t", st, 0, numFrames, printWarning)); buf.append(LINE_SEPARATOR); } return buf.toString(); } static String stackTraceAllStr(int numFrames) { return stackTraceAllStr(numFrames, false); } static void stackTraceAll(int numFrames) { getRt().send(stackTraceAllStr(numFrames, true)); } static String stackTraceStr(StackTraceElement[] st, int strip, int numFrames) { return stackTraceStr(null, st, strip, numFrames, false); } static String stackTraceStr(String prefix, StackTraceElement[] st, int strip, int numFrames) { return stackTraceStr(prefix, st, strip, numFrames, false); } private static String stackTraceStr( String prefix, StackTraceElement[] st, int strip, int numFrames, boolean printWarning) { strip = strip > 0 ? strip + THRD_DUMP_FRAMES : 0; numFrames = numFrames > 0 ? numFrames : st.length - strip; int limit = strip + numFrames; limit = Math.min(limit, st.length); if (prefix == null) { prefix = ""; } StringBuilder buf = new StringBuilder(); for (int i = strip; i < limit; i++) { buf.append(prefix); buf.append(st[i]); buf.append(LINE_SEPARATOR); } if (printWarning && limit < st.length) { buf.append(prefix); buf.append(st.length - limit); buf.append(" more frame(s) ..."); buf.append(LINE_SEPARATOR); } return buf.toString(); } static void stackTrace(StackTraceElement[] st, int strip, int numFrames) { stackTrace(null, st, strip, numFrames); } static void stackTrace(String prefix, StackTraceElement[] st, int strip, int numFrames) { getRt().send(stackTraceStr(prefix, st, strip, numFrames, true)); } // print/println functions static void print(String str) { getRt().send(str); } static void println(String str) { getRt().send(str + LINE_SEPARATOR); } static void println() { getRt().send(LINE_SEPARATOR); } static String property(String name) { return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(name)); } static Properties properties() { return AccessController.doPrivileged( (PrivilegedAction) () -> System.getProperties()); } static String getenv(String name) { return AccessController.doPrivileged((PrivilegedAction) () -> System.getenv(name)); } static Map getenv() { return AccessController.doPrivileged( (PrivilegedAction>) () -> System.getenv()); } static MemoryUsage heapUsage() { return getRt().getMemoryMXBean().getHeapMemoryUsage(); } static MemoryUsage nonHeapUsage() { return getRt().getMemoryMXBean().getNonHeapMemoryUsage(); } static long finalizationCount() { return getRt().getMemoryMXBean().getObjectPendingFinalizationCount(); } static long vmStartTime() { return getRt().getRuntimeMXBean().getStartTime(); } static long vmUptime() { return getRt().getRuntimeMXBean().getUptime(); } static List getInputArguments() { return getRt().getRuntimeMXBean().getInputArguments(); } static String getVmVersion() { return getRt().getRuntimeMXBean().getVmVersion(); } static boolean isBootClassPathSupported() { return getRt().getRuntimeMXBean().isBootClassPathSupported(); } static String getBootClassPath() { return getRt().getRuntimeMXBean().getBootClassPath(); } static long getThreadCount() { return getRt().getThreadMXBean().getThreadCount(); } static long getPeakThreadCount() { return getRt().getThreadMXBean().getPeakThreadCount(); } static long getTotalStartedThreadCount() { return getRt().getThreadMXBean().getTotalStartedThreadCount(); } static long getDaemonThreadCount() { return getRt().getThreadMXBean().getDaemonThreadCount(); } static long getCurrentThreadCpuTime() { ThreadMXBean threadMXBean = getRt().getThreadMXBean(); threadMXBean.setThreadCpuTimeEnabled(true); return threadMXBean.getCurrentThreadCpuTime(); } static long getCurrentThreadUserTime() { ThreadMXBean threadMXBean = getRt().getThreadMXBean(); threadMXBean.setThreadCpuTimeEnabled(true); return threadMXBean.getCurrentThreadUserTime(); } static void dumpHeap(String fileName, boolean live) { try { String name = getRt().resolveFileName(fileName); getRt().getHotspotMBean().dumpHeap(name, live); } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } static long getTotalGcTime() { long totalGcTime = 0; for (GarbageCollectorMXBean gcBean : getRt().getGCMBeans()) { totalGcTime += gcBean.getCollectionTime(); } return totalGcTime; } static String getMemoryPoolUsage(String poolFormat) { if (poolFormat == null) { poolFormat = "%1$s;%2$d;%3$d;%4$d;%5$d"; } List memPoolList = getRt().getMemoryPoolMXBeans(); Object[][] poolOutput = new Object[memPoolList.size()][5]; StringBuilder membuffer = new StringBuilder(); for (int i = 0; i < memPoolList.size(); i++) { MemoryPoolMXBean memPool = memPoolList.get(i); poolOutput[i][0] = memPool.getName(); poolOutput[i][1] = memPool.getUsage().getMax(); poolOutput[i][2] = memPool.getUsage().getUsed(); poolOutput[i][3] = memPool.getUsage().getCommitted(); poolOutput[i][4] = memPool.getUsage().getInit(); } for (Object[] memPoolOutput : poolOutput) { membuffer.append(String.format(poolFormat, memPoolOutput)).append("\n"); } return membuffer.toString(); } static double getSystemLoadAverage() { return getRt().getOperatingSystemMXBean().getSystemLoadAverage(); } static long getProcessCPUTime() { OperatingSystemMXBean mbean = getRt().getOperatingSystemMXBean(); if (mbean instanceof com.sun.management.OperatingSystemMXBean) { return ((com.sun.management.OperatingSystemMXBean) mbean).getProcessCpuTime(); } return -1; } static void serialize(Object obj, String fileName) { try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(getRt().resolveFileName(fileName))); try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(obj); } } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } static String toXML(Object obj) { return getRt().toXML(obj); } static void writeXML(Object obj, String fileName) { getRt().writeXML(obj, fileName); } static void writeDOT(Object obj, String fileName) { getRt().writeDOT(obj, fileName); } static void deadlocks(boolean stackTrace) { ThreadMXBean mbean = getRt().getThreadMXBean(); if (mbean.isSynchronizerUsageSupported()) { long[] tids = mbean.findDeadlockedThreads(); if (tids != null && tids.length > 0) { ThreadInfo[] infos = mbean.getThreadInfo(tids, true, true); StringBuilder sb = new StringBuilder(); for (ThreadInfo ti : infos) { sb.append("\"") .append(ti.getThreadName()) .append("\"" + " Id=") .append(ti.getThreadId()) .append(" in ") .append(ti.getThreadState()); if (ti.getLockName() != null) { sb.append(" on lock=").append(ti.getLockName()); } if (ti.isSuspended()) { sb.append(" (suspended)"); } if (ti.isInNative()) { sb.append(" (running in native)"); } if (ti.getLockOwnerName() != null) { sb.append(INDENT) .append(" owned by ") .append(ti.getLockOwnerName()) .append(" Id=") .append(ti.getLockOwnerId()); sb.append(LINE_SEPARATOR); } if (stackTrace) { // print stack trace with locks StackTraceElement[] stacktrace = ti.getStackTrace(); MonitorInfo[] monitors = ti.getLockedMonitors(); for (int i = 0; i < stacktrace.length; i++) { StackTraceElement ste = stacktrace[i]; sb.append(INDENT).append("at ").append(ste); sb.append(LINE_SEPARATOR); for (MonitorInfo mi : monitors) { if (mi.getLockedStackDepth() == i) { sb.append(INDENT).append(" - locked ").append(mi); sb.append(LINE_SEPARATOR); } } } sb.append(LINE_SEPARATOR); } LockInfo[] locks = ti.getLockedSynchronizers(); sb.append(INDENT).append("Locked synchronizers: count = ").append(locks.length); sb.append(LINE_SEPARATOR); for (LockInfo li : locks) { sb.append(INDENT).append(" - ").append(li); sb.append(LINE_SEPARATOR); } sb.append(LINE_SEPARATOR); } getRt().send(sb.toString()); } } } static int dtraceProbe(String s1, String s2, int i1, int i2) { if (getRt().isDTraceEnabled()) { return dtraceProbe0(s1, s2, i1, i2); } else { return 0; } } // called from instrumentation initialization code public static JfrEvent.Factory createEventFactory(JfrEvent.Template template) { return getRt().createEventFactory(template); } public static boolean isBootstrapClass(String className) { return getRt().isBootstrapClass(className); } static void printSnapshot(String name, Profiler.Snapshot snapshot) { getRt().sendGridData(name, snapshot.getGridData()); } /** * Prints profiling snapshot using the provided format * * @param name The name of the aggregation to be used in the textual output * @param snapshot The snapshot to print * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, * java.lang.Object[]) } behaviour with the addition of the ability to address the key title * as a 0-indexed item * @see String#format(java.lang.String, java.lang.Object[]) */ static void printSnapshot(String name, Profiler.Snapshot snapshot, String format) { getRt().sendGridData(name, snapshot.getGridData(), format); } /** * @see BTraceUtils.Profiling#newProfiler() */ static Profiler newProfiler() { return getRt().newProfiler(); } /** * @see BTraceUtils.Profiling#newProfiler(int) */ static Profiler newProfiler(int expectedMethodCnt) { return getRt().newProfiler(expectedMethodCnt); } /** * @see BTraceUtils.Profiling#recordEntry(Profiler, java.lang.String) */ static void recordEntry(Profiler profiler, String methodName) { profiler.recordEntry(methodName); } // profiling related methods /** * @see BTraceUtils.Profiling#recordExit(Profiler, java.lang.String, long) */ static void recordExit(Profiler profiler, String methodName, long duration) { profiler.recordExit(methodName, duration); } /** * @see BTraceUtils.Profiling#snapshot(Profiler) */ static Profiler.Snapshot snapshot(Profiler profiler) { return profiler.snapshot(); } /** * @see BTraceUtils.Profiling#snapshotAndReset(Profiler) */ static Profiler.Snapshot snapshotAndReset(Profiler profiler) { return profiler.snapshot(true); } static void resetProfiler(Profiler profiler) { profiler.reset(); } static ClassLoader getCallerClassloader(int stackDec) { return getRt().getCallerClassLoader(stackDec + 1); } public static Class getCallerClass(int stackDec) { return getRt().getCallerClass(stackDec + 1); } // private methods below this point // raise DTrace USDT probe private static native int dtraceProbe0(String s1, String s2, int i1, int i2); /** * Common BTrace runtime interface allowing access from various subsystems (core, runtime, * instrumentation, clients) */ public interface Impl { void send(String msg); void sendCommand(Object cmd); void sendNumberData(String name, Number value); void sendNumberMapData(String name, Map data); void sendStringMapData(String name, Map data); void sendGridData(String name, List data); void sendGridData(String name, List data, String format); boolean enter(); void leave(); int getInstrumentationLevel(); void setInstrumentationLevel(int level); void handleException(Throwable th); int speculation(); void speculate(int id); void commit(int id); void discard(int id); void exit(int exitCode); long sizeof(Object obj); int $length(); String $(int n); String $(String key); String toXML(Object obj); void writeXML(Object obj, String fileName); void writeDOT(Object obj, String fileName); Profiler newProfiler(); Profiler newProfiler(int expectedMethodCnt); int perfInt(String name); long perfLong(String name); String perfString(String name); String resolveFileName(String name); boolean isDTraceEnabled(); void handleEvent(Object cmd); void handleExit(int i); void shutdownCmdLine(); List getMemoryPoolMXBeans(); HotSpotDiagnosticMXBean getHotspotMBean(); MemoryMXBean getMemoryMXBean(); RuntimeMXBean getRuntimeMXBean(); ThreadMXBean getThreadMXBean(); OperatingSystemMXBean getOperatingSystemMXBean(); List getGCMBeans(); Class defineClass(byte[] code); ClassLoader getCallerClassLoader(int stackDec); Class getCallerClass(int stackDec); JfrEvent.Factory createEventFactory(JfrEvent.Template template); int version(); boolean isBootstrapClass(String className); String getClassName(); } public interface BTraceRuntimeAccessor { Impl getRt(); } private static final class BTraceAtomicInteger extends AtomicInteger { BTraceAtomicInteger(int initVal) { super(initVal); } } private static final class BTraceAtomicLong extends AtomicLong { BTraceAtomicLong(long initVal) { super(initVal); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/BTraceRuntimeBridge.java ================================================ /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.core; /** * Minimal bootstrap-visible bridge used by injected code to reach the runtime implementation. */ public interface BTraceRuntimeBridge { void start(); void leave(); void handleException(Throwable th); boolean isDisabled(); void newPerfCounter(Object value, String name, String desc); int getPerfInt(String name); void putPerfInt(int value, String name); float getPerfFloat(String name); void putPerfFloat(float value, String name); long getPerfLong(String name); void putPerfLong(long value, String name); String getPerfString(String name); void putPerfString(String value, String name); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/BTraceUtils.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import java.io.Serializable; import java.lang.management.MemoryUsage; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collection; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import jdk.jfr.Event; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.jfr.JfrEvent; import org.openjdk.btrace.core.types.AnyType; /** * This class is an all-in-one wrapper for BTrace DSL methods * * @author A. Sundararajan * @author Jaroslav Bachorik */ public class BTraceUtils { // standard stack depth decrement for figuring out the caller class calls private static final int STACK_DEC = 2; static { BTraceRuntime.initUnsafe(); } // do not allow object creation! private BTraceUtils() {} // Thread and stack access /** * Tests whether this thread has been interrupted. The interrupted status of the thread is * unaffected by this method. * *

A thread interruption ignored because a thread was not alive at the time of the interrupt * will be reflected by this method returning false. * * @return true if this thread has been interrupted; false otherwise. */ public static boolean isInteruppted() { return Threads.isInteruppted(); } /** Prints the java stack trace of the current thread. */ public static void jstack() { Threads.jstack(2, -1); } /** * Prints the java stack trace of the current thread. But, at most given number of frames. * * @param numFrames number of frames to be printed. When this is negative all frames are printed. */ public static void jstack(int numFrames) { Threads.jstack(2, numFrames); } /** Prints Java stack traces of all the Java threads. */ public static void jstackAll() { Threads.jstackAll(2, -1); } /** * Prints Java stack traces of all the Java threads. But, at most given number of frames. * * @param numFrames number of frames to be printed. When this is negative all frames are printed. */ public static void jstackAll(int numFrames) { Threads.jstackAll(2, numFrames); } /** * Returns the stack trace of current thread as a String. * * @return the stack trace as a String. */ public static String jstackStr() { return Threads.jstackStr(2, -1); } /** * Returns the stack trace of the current thread as a String but includes at most the given number * of frames. * * @param numFrames number of frames to be included. When this is negative all frames are * included. * @return the stack trace as a String. */ public static String jstackStr(int numFrames) { return Threads.jstackStr(2, numFrames); } /** * Returns the stack traces of all Java threads as a String. * * @return the stack traces as a String. */ public static String jstackAllStr() { return Threads.jstackAllStr(); } /** * Returns at most given number of frames in stack traces of all threads as a String. * * @param numFrames number of frames to be included. When this is negative all frames are * included. * @return the stack traces as a String. */ public static String jstackAllStr(int numFrames) { return Threads.jstackAllStr(numFrames); } /** * Prints the stack trace of the given exception object. * * @param exception throwable for which stack trace is printed. */ public static void jstack(Throwable exception) { Threads.jstack(exception); } /** * Prints the stack trace of the given exception object. But, prints atmost given number of * frames. * * @param exception throwable for which stack trace is printed. * @param numFrames maximum number of frames to be printed. */ public static void jstack(Throwable exception, int numFrames) { Threads.jstack(exception, numFrames); } /** * Returns the stack trace of given exception object as a String. * * @param exception the throwable for which stack trace is returned. */ public static String jstackStr(Throwable exception) { return Threads.jstackStr(exception); } /** * Returns stack trace of given exception object as a String. * * @param exception throwable for which stack trace is returned. * @param numFrames maximum number of frames to be returned. */ public static String jstackStr(Throwable exception, int numFrames) { return Threads.jstackStr(exception, numFrames); } /** * Returns a reference to the currently executing thread object. * * @return the currently executing thread. */ public static Thread currentThread() { return Threads.currentThread(); } /** * Returns the identifier of the given Thread. The thread ID is a positive long number * generated when the given thread was created. The thread ID is unique and remains unchanged * during its lifetime. When a thread is terminated, the thread ID may be reused. */ public static long threadId(Thread thread) { return Threads.threadId(thread); } /** * Returns the state of the given thread. This method is designed for use in monitoring of the * system state, not for synchronization control. */ public static Thread.State threadState(Thread thread) { return Threads.threadState(thread); } /** * Returns true if and only if the current thread holds the monitor lock on the specified * object. * *

This method is designed to allow a program to assert that the current thread already holds a * specified lock: * *

   *     assert Thread.holdsLock(obj);
   * 
* * @param obj the object on which to test lock ownership * @return true if the current thread holds the monitor lock on the specified object. * @throws NullPointerException if obj is null */ public static boolean holdsLock(Object obj) { return Threads.holdsLock(obj); } /** Prints the Java level deadlocks detected (if any). */ public static void deadlocks() { Threads.deadlocks(); } /** * Prints deadlocks detected (if any). Optionally prints stack trace of the deadlocked threads. * * @param stackTrace boolean flag to specify whether to print stack traces of deadlocked threads * or not. */ public static void deadlocks(boolean stackTrace) { Threads.deadlocks(stackTrace); } /** * Returns the name of the given thread. * * @param thread thread whose name is returned */ public static String name(Thread thread) { return Threads.name(thread); } // class loader access /** * Returns the class loader for the given class. Some implementations may use null to represent * the bootstrap class loader. This method will return null in such implementations if this class * was loaded by the bootstrap class loader. * * @param clazz the Class for which the class loader is returned */ public static ClassLoader loader(Class clazz) { return clazz.getClassLoader(); } /** * Returns the parent class loader of the given loader. Some implementations may use null * to represent the bootstrap class loader. This method will return null in such * implementations if this class loader's parent is the bootstrap class loader. * * @param loader the loader for which the parent loader is returned * @return The parent ClassLoader */ public static ClassLoader parentLoader(ClassLoader loader) { return loader.getParent(); } // java.lang.Object methods /** * Returns a string representation of the object. In general, the toString method * returns a string that "textually represents" this object. The result should be a concise but * informative representation that is easy for a person to read. For bootstrap classes, returns * the result of calling Object.toString() override. For non-bootstrap classes, default toString() * value [className@hashCode] is returned. * * @param obj the object whose string representation is returned * @return a string representation of the given object. */ public static String str(Object obj) { return Strings.str(obj); } public static String str(Object[] array) { return Strings.str(array); } /** * Returns identity string of the form class-name@identity-hash * * @param obj object for which identity string is returned * @return identity string */ public static String identityStr(Object obj) { int hashCode = java.lang.System.identityHashCode(obj); return obj.getClass().getName() + "@" + Integer.toHexString(hashCode); } /** * Returns a hash code value for the object. This method is supported for the benefit of * hashtables such as those provided by java.util.Hashtable. For bootstrap classes, * returns the result of calling Object.hashCode() override. For non-bootstrap classes, the * identity hash code is returned. * * @param obj the Object whose hash code is returned. * @return a hash code value for the given object. */ public static int hash(Object obj) { if (obj.getClass().getClassLoader() == null) { return obj.hashCode(); } else { return java.lang.System.identityHashCode(obj); } } /** * Returns the same hash code for the given object as would be returned by the default method * hashCode(), whether or not the given object's class overrides hashCode(). The hash code for the * null reference is zero. * * @param obj object for which the hashCode is to be calculated * @return the hashCode */ public static int identityHashCode(Object obj) { return java.lang.System.identityHashCode(obj); } /** * Indicates whether two given objects are "equal to" one another. For bootstrap classes, returns * the result of calling Object.equals() override. For non-bootstrap classes, the reference * identity comparison is done. * * @param obj1 first object to compare equality * @param obj2 second object to compare equality * @return true if the given objects are equal; false otherwise. */ public static boolean compare(Object obj1, Object obj2) { return BTraceRuntime.compare(obj1, obj2); } // reflection /** * Returns the runtime class of the given Object. * * @param obj the Object whose Class is returned * @return the Class object of given object */ public static Class classOf(Object obj) { return Reflective.classOf(obj); } /** * Checks whether the provided object is an instance of the named class. Note: this method * can be rather CPU intensive, use with caution * * @param obj the object to check * @param className the class name; as a special case {@code void} can be provided to check * whether the instance is a void value wrapper - {@linkplain AnyType#VOID} * @return {@code true} if the object can be assigned to an instance of 'className' type * @since 1.3.5 */ public static boolean instanceOf(Object obj, String className) { return BTraceRuntime.instanceOf(obj, className); } /** * Returns the Class object representing the class or interface that declares the field * represented by the given Field object. * * @param field whose declaring Class is returned */ public static Class declaringClass(Field field) { return Reflective.declaringClass(field); } /** Returns the name of the given Class object. */ public static String name(Class clazz) { return Reflective.name(clazz); } /** * Returns the name of the Field object. * * @param field Field for which name is returned * @return name of the given field */ public static String name(Field field) { return Reflective.name(field); } /** * Returns the type of the Field object. * * @param field Field for which type is returned * @return type of the given field */ public static Class type(Field field) { return Reflective.type(field); } /** Returns the access flags of the given Class. */ public static int accessFlags(Class clazz) { return Reflective.accessFlags(clazz); } /** Returns the access flags of the given Field. */ public static int accessFlags(Field field) { return Reflective.accessFlags(field); } /** Returns the current context class loader */ public static ClassLoader contextClassLoader() { return Reflective.contextClassLoader(); } // get Class of the given name /** Returns Class object for given class name. */ public static Class classForName(String name) { return Reflective.classForName(name); } /** Returns the Class for the given class name using the given class loader. */ public static Class classForName(String name, ClassLoader cl) { return Reflective.classForName(name, cl); } /** * Determines if the class or interface represented by the first Class object is * either the same as, or is a superclass or superinterface of, the class or interface represented * by the second Class parameter. It returns true if so; otherwise it * returns false. */ public static boolean isAssignableFrom(Class a, Class b) { return Reflective.isAssignableFrom(a, b); } /** * Determines if the specified Object is assignment-compatible with the object * represented by the specified Class. This method is the dynamic equivalent of the * Java language instanceof operator. The method returns true if the * specified Object argument is non-null and can be cast to the reference type * represented by this Class object without raising a ClassCastException. * It returns false otherwise. * * @param clazz the class that is checked. * @param obj the object to check. * @return true if obj is an instance of the given class. */ public static boolean isInstance(Class clazz, Object obj) { return Reflective.isInstance(clazz, obj); } /** * Returns the Class representing the superclass of the entity (class, interface, * primitive type or void) represented by the given Class. If the given Class * represents either the Object class, an interface, a primitive type, or * void, then null is returned. If the given object represents an array class then the Class * object representing the Object class is returned. * * @param clazz the Class whose super class is returned. * @return the superclass of the class represented by the given object. */ public static Class getSuperclass(Class clazz) { return Reflective.getSuperclass(clazz); } /** * Determines if the specified Class object represents an interface type. * * @param clazz the Class object to check. * @return true if the Class represents an interface; false otherwise. */ public static boolean isInterface(Class clazz) { return Reflective.isInterface(clazz); } /** * Determines if the given Class object represents an array class. * * @param clazz Class object to check. * @return true if the given object represents an array class; false * otherwise. */ public static boolean isArray(Class clazz) { return Reflective.isArray(clazz); } /** Returns whether the given Class represent primitive type or not. */ public static boolean isPrimitive(Class clazz) { return Reflective.isPrimitive(clazz); } /** returns component type of an array Class. */ public static Class getComponentType(Class clazz) { return Reflective.getComponentType(clazz); } // Accessing fields by reflection /** * Returns a Field object that reflects the specified declared field of the class or * interface represented by the given Class object. The name parameter * is a String that specifies the simple name of the desired field. Returns * null on not finding field if throwException parameter is false. Else throws * a RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @param throwException whether to throw exception on failing to find field or not * @return the Field object for the specified field in this class */ public static Field field(Class clazz, String name, boolean throwException) { return Reflective.field(clazz, name, throwException); } /** * Returns a Field object that reflects the specified declared field of the class or * interface represented by the given Class object. The name parameter * is a String that specifies the simple name of the desired field. Throws a * RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @return the Field object for the specified field in this class */ public static Field field(Class clazz, String name) { return Reflective.field(clazz, name); } /** * Returns a Field object that reflects the specified declared field of the class or * interface represented by the given Class object. The name parameter * is a String that specifies the simple name of the desired field. Returns * null on not finding field if throwException parameter is false. Else throws * a RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @param throwException whether to throw exception on failing to find field or not * @return the Field object for the specified field in this class */ public static Field field(String clazz, String name, boolean throwException) { ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); return Reflective.field(classForName(clazz, callerLoader), name, throwException); } /** * Returns a Field object that reflects the specified declared field of the class or * interface represented by the given Class object. The name parameter * is a String that specifies the simple name of the desired field. Throws a * RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @return the Field object for the specified field in this class */ public static Field field(String clazz, String name) { ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); return Reflective.field(classForName(clazz, callerLoader), name); } // field value get methods /** * Gets the value of a static byte field. * * @param field Field object whose value is returned. * @return the value of the byte field */ public static byte getByte(Field field) { return Reflective.getByte(field); } /** * Gets the value of an instance byte field. * * @param field Field object whose value is returned. * @param obj the object to extract the byte value from * @return the value of the byte field */ public static byte getByte(Field field, Object obj) { return Reflective.getByte(field, obj); } /** * Gets the value of a static short field. * * @param field Field object whose value is returned. * @return the value of the short field */ public static short getShort(Field field) { return Reflective.getShort(field); } /** * Gets the value of an instance short field. * * @param field Field object whose value is returned. * @param obj the object to extract the short value from * @return the value of the short field */ public static short getShort(Field field, Object obj) { return Reflective.getShort(field, obj); } /** * Gets the value of a static int field. * * @param field Field object whose value is returned. * @return the value of the int field */ public static int getInt(Field field) { return Reflective.getInt(field); } /** * Gets the value of an instance int field. * * @param field Field object whose value is returned. * @param obj the object to extract the int value from * @return the value of the int field */ public static int getInt(Field field, Object obj) { return Reflective.getInt(field, obj); } /** * Gets the value of a static long field. * * @param field Field object whose value is returned. * @return the value of the long field */ public static long getLong(Field field) { return Reflective.getLong(field); } /** * Gets the value of an instance long field. * * @param field Field object whose value is returned. * @param obj the object to extract the long value from * @return the value of the long field */ public static long getLong(Field field, Object obj) { return Reflective.getLong(field, obj); } /** * Gets the value of a static float field. * * @param field Field object whose value is returned. * @return the value of the float field */ public static float getFloat(Field field) { return Reflective.getFloat(field); } /** * Gets the value of an instance float field. * * @param field Field object whose value is returned. * @param obj the object to extract the float value from * @return the value of the float field */ public static float getFloat(Field field, Object obj) { return Reflective.getFloat(field, obj); } /** * Gets the value of a static double field. * * @param field Field object whose value is returned. * @return the value of the double field */ public static double getDouble(Field field) { return Reflective.getDouble(field); } /** * Gets the value of an instance double field. * * @param field Field object whose value is returned. * @param obj the object to extract the double value from * @return the value of the double field */ public static double getDouble(Field field, Object obj) { return Reflective.getDouble(field, obj); } /** * Gets the value of a static boolean field. * * @param field Field object whose value is returned. * @return the value of the boolean field */ public static boolean getBoolean(Field field) { return Reflective.getBoolean(field); } /** * Gets the value of an instance boolean field. * * @param field Field object whose value is returned. * @param obj the object to extract the boolean value from * @return the value of the boolean field */ public static boolean getBoolean(Field field, Object obj) { return Reflective.getBoolean(field, obj); } /** * Gets the value of a static char field. * * @param field Field object whose value is returned. * @return the value of the char field */ public static char getChar(Field field) { return Reflective.getChar(field); } /** * Gets the value of an instance char field. * * @param field Field object whose value is returned. * @param obj the object to extract the char value from * @return the value of the char field */ public static char getChar(Field field, Object obj) { return Reflective.getChar(field, obj); } /** * Gets the value of a static reference field. * * @param field Field object whose value is returned. * @return the value of the reference field */ public static Object get(Field field) { return Reflective.get(field); } /** * Gets the value of an instance reference field. * * @param field Field object whose value is returned. * @param obj the object to extract the reference value from * @return the value of the reference field */ public static Object get(Field field, Object obj) { return Reflective.get(field, obj); } // weak, soft references /** * Creates and returns a weak reference to the given object. * * @param obj object for which a weak reference is created. * @return a weak reference to the given object. */ public static WeakReference weakRef(Object obj) { return References.weakRef(obj); } /** * Creates and returns a soft reference to the given object. * * @param obj object for which a soft reference is created. * @return a soft reference to the given object. */ public static SoftReference softRef(Object obj) { return References.softRef(obj); } /** * Returns the given reference object's referent. If the reference object has been cleared, either * by the program or by the garbage collector, then this method returns null. * * @param ref reference object whose referent is returned. * @return The object to which the reference refers, or null if the reference object * has been cleared. */ public static Object deref(Reference ref) { return References.deref(ref); } // probe point access /** * Returns the Class object of the currently probed (or traced) class. * * @deprecated Since 1.1. Use {@linkplain ProbeClassName} and {@linkplain Self} annotations * instead */ @Deprecated public static Class probeClass() { return BTraceRuntime.getCallerClass(STACK_DEC); } /** * Returns the currently probed method's name. * * @deprecated Since 1.1. Use {@linkplain ProbeMethodName} annotation instead */ @Deprecated public static String probeMethod() { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); if (stack.length >= 4) { return stack[3].getMethodName(); } else { return null; } } /** Returns the currently probed source line number (if available). */ public static int probeLine() { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); if (stack.length >= 4) { return stack[3].getLineNumber(); } else { return -1; } } // printing values /** * Prints the given Map. * * @param map Map that is printed. */ public static void printMap(Map map) { BTraceRuntime.printMap(map); } /** * Prints the given Map. * * @param name - the name of the map * @param data - the map data */ public static void printStringMap(String name, Map data) { BTraceRuntime.printStringMap(name, data); } /** * Prints the given Map. * * @param name - the name of the map * @param data - the map data */ public static void printNumberMap(String name, Map data) { BTraceRuntime.printNumberMap(name, data); } /** * Prints a number. * * @param name - name of the number data * @param value - value of the numerical data */ public static void printNumber(String name, Number value) { BTraceRuntime.printNumber(name, value); } /** Prints the elements of the given array as comma separated line bounded by '[' and ']'. */ public static void printArray(Object[] array) { StringBuilder buf = new StringBuilder(); buf.append('['); for (Object obj : array) { buf.append(Strings.str(obj)); buf.append(", "); } buf.append(']'); println(buf.toString()); } /** * Print all instance fields of an object as name-value pairs. Includes the inherited fields as * well. * * @param obj Object whose fields are printed. */ public static void printFields(Object obj) { Reflective.printFields(obj, false); } /** * Print all instance fields of an object as name-value pairs. Includes the inherited fields as * well. Optionally, prints name of the declaring class before each field - so that if same named * field in super class chain may be disambiguated. * * @param obj Object whose fields are printed. * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. */ public static void printFields(Object obj, boolean classNamePrefix) { Reflective.printFields(obj, classNamePrefix); } /** * Print all static fields of the class as name-value pairs. Includes the inherited fields as * well. * * @param clazz Class whose static fields are printed. */ public static void printStaticFields(Class clazz) { Reflective.printStaticFields(clazz, false); } /** * Print all static fields of the class as name-value pairs. Includes the inherited fields as * well. Optionally, prints name of the declaring class before each field - so that if same named * field in super class chain may be disambiguated. * * @param clazz Class whose static fields are printed. * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. */ public static void printStaticFields(Class clazz, boolean classNamePrefix) { Reflective.printStaticFields(clazz, classNamePrefix); } // various print methods public static void print(Object obj) { BTraceRuntime.print(Strings.str(obj)); } /** * Prints a boolean value. The string produced by {@link * java.lang.String#valueOf(boolean)} is sent to BTrace client for "printing". * * @param b The boolean to be printed */ public static void print(boolean b) { print(Boolean.valueOf(b)); } /** * Prints a character. The string produced by {@link * java.lang.Character#valueOf(char)} is sent to BTrace client for "printing". * * @param c The char to be printed */ public static void print(char c) { print(Character.valueOf(c)); } /** * Prints an integer. The string produced by {@link * java.lang.String#valueOf(int)} is sent to BTrace client for "printing". * * @param i The int to be printed * @see java.lang.Integer#toString(int) */ public static void print(int i) { print(Integer.valueOf(i)); } /** * Prints a long integer. The string produced by {@link * java.lang.String#valueOf(long)} is sent to BTrace client for "printing". * * @param l The long to be printed * @see java.lang.Long#toString(long) */ public static void print(long l) { print(Long.valueOf(l)); } /** * Prints a floating-point number. The string produced by {@link * java.lang.String#valueOf(float)} is sent to BTrace client for "printing". * * @param f The float to be printed * @see java.lang.Float#toString(float) */ public static void print(float f) { print(Float.valueOf(f)); } /** * Prints a double-precision floating-point number. The string produced by * {@link java.lang.String#valueOf(double)} is sent to BTrace client for "printing". * * @param d The double to be printed * @see java.lang.Double#toString(double) */ public static void print(double d) { print(Double.valueOf(d)); } /** Prints the given object and then prints a newline */ public static void println(Object obj) { BTraceRuntime.println(Strings.str(obj)); } /** * Prints a boolean and then terminate the line. This method behaves as though it invokes * {@link #print(boolean)} and then {@link #println()}. * * @param b The boolean to be printed */ public static void println(boolean b) { println(Boolean.valueOf(b)); } /** * Prints a character and then terminate the line. This method behaves as though it invokes * {@link #print(char)} and then {@link #println()}. * * @param c The char to be printed. */ public static void println(char c) { println(Character.valueOf(c)); } /** * Prints an integer and then terminate the line. This method behaves as though it invokes * {@link #print(int)} and then {@link #println()}. * * @param i The int to be printed. */ public static void println(int i) { println(Integer.valueOf(i)); } /** * Prints a long and then terminate the line. This method behaves as though it invokes * {@link #print(long)} and then {@link #println()}. * * @param l a The long to be printed. */ public static void println(long l) { println(Long.valueOf(l)); } /** * Prints a float and then terminate the line. This method behaves as though it invokes * {@link #print(float)} and then {@link #println()}. * * @param f The float to be printed. */ public static void println(float f) { println(Float.valueOf(f)); } /** * Prints a double and then terminate the line. This method behaves as though it invokes * {@link #print(double)} and then {@link #println()}. * * @param d The double to be printed. */ public static void println(double d) { println(Double.valueOf(d)); } /** * Terminates the current line by writing the line separator string. The line separator string is * defined by the system property line.separator, and is not necessarily a single * newline character ('\n'). */ public static void println() { BTraceRuntime.println(); } /** * Returns the start time of the Java virtual machine in milliseconds. This method returns the * approximate time when the Java virtual machine started. * * @return start time of the Java virtual machine in milliseconds. */ public static long vmStartTime() { return Sys.VM.vmStartTime(); } /** * Returns the uptime of the Java virtual machine in milliseconds. * * @return uptime of the Java virtual machine in milliseconds. */ public static long vmUptime() { return Sys.VM.vmUptime(); } /** * Returns the current time in milliseconds. Note that while the unit of time of the return value * is a millisecond, the granularity of the value depends on the underlying operating system and * may be larger. For example, many operating systems measure time in units of tens of * milliseconds. * * @return the difference, measured in milliseconds, between the current time and midnight, * January 1, 1970 UTC. */ public static long timeMillis() { return Time.millis(); } /** * Returns the current value of the most precise available system timer, in nanoseconds. * *

This method can only be used to measure elapsed time and is not related to any other notion * of system or wall-clock time. The value returned represents nanoseconds since some fixed but * arbitrary time (perhaps in the future, so values may be negative). This method provides * nanosecond precision, but not necessarily nanosecond accuracy. No guarantees are made about how * frequently values change. Differences in successive calls that span greater than approximately * 292 years (263 nanoseconds) will not accurately compute elapsed time due to * numerical overflow. * * @return The current value of the system timer, in nanoseconds. */ public static long timeNanos() { return Time.nanos(); } /** * Generates a string getTimestamp (current date&time) * * @param format The format to be used - see {@linkplain SimpleDateFormat} * @return Returns a string representing current date&time * @since 1.1 */ public static String timestamp(String format) { return Time.timestamp(format); } /** * Generates a string getTimestamp (current date&time) in the default system format * * @return Returns a string representing current date&time * @since 1.1 */ public static String timestamp() { return Time.timestamp(); } // String utilities public static boolean startsWith(String s, String start) { return Strings.startsWith(s, start); } public static boolean endsWith(String s, String end) { return Strings.endsWith(s, end); } /** * This is synonym to "concat". * * @see #concat(String, String) */ public static String strcat(String str1, String str2) { return Strings.strcat(str1, str2); } /** Concatenates the specified strings together. */ public static String concat(String str1, String str2) { return Strings.concat(str1, str2); } /** * Compares two strings lexicographically. The comparison is based on the Unicode value of each * character in the strings. The character sequence represented by the first String * object is compared lexicographically to the character sequence represented by the second * string. The result is a negative integer if the first String object * lexicographically precedes the second string. The result is a positive integer if the first * String object lexicographically follows the second string. The result is zero if * the strings are equal; compareTo returns 0 exactly when the {@link * String#equals(Object)} method would return true. */ public static int compareTo(String str1, String str2) { return Strings.compareTo(str1, str2); } /** * This is synonym to "compareTo" method. * * @see #compareTo */ public static int strcmp(String str1, String str2) { return Strings.strcmp(str1, str2); } /** * Compares two strings lexicographically, ignoring case differences. This method returns an * integer whose sign is that of calling compareTo with normalized versions of the * strings where case differences have been eliminated by calling * Character.toLowerCase(Character.toUpperCase(character)) on each character. */ public static int compareToIgnoreCase(String str1, String str2) { return Strings.compareToIgnoreCase(str1, str2); } /** * This is synonym to "compareToIgnoreCase". * * @see #compareToIgnoreCase */ public static int stricmp(String str1, String str2) { return Strings.stricmp(str1, str2); } /** Find String within String */ public static int strstr(String str1, String str2) { return Strings.strstr(str1, str2); } public static int indexOf(String str1, String str2) { return Strings.indexOf(str1, str2); } public static int lastIndexOf(String str1, String str2) { return Strings.lastIndexOf(str1, str2); } /** Substring */ public static String substr(String str, int start, int length) { return Strings.substr(str, start, length); } public static String substr(String str, int start) { return Strings.substr(str, start); } /** * Returns the length of the given string. The length is equal to the number of Unicode code units in the string. * * @param str String whose length is calculated. * @return the length of the sequence of characters represented by this object. */ public static int length(String str) { return Strings.length(str); } /** * This is synonym for "length". * * @see #length(String) */ public static int strlen(String str) { return Strings.strlen(str); } // regular expression matching /** * Compiles the given regular expression into a pattern. * * @param regex The expression to be compiled * @throws PatternSyntaxException If the expression's syntax is invalid */ public static Pattern regexp(String regex) { return Strings.regexp(regex); } /** * This is synonym for "regexp". * * @see #regexp(String) */ public static Pattern pattern(String regex) { return Strings.pattern(regex); } /** * Compiles the given regular expression into a pattern with the given flags. * * @param regex The expression to be compiled * @param flags Match flags, a bit mask that may include {@link Pattern#CASE_INSENSITIVE}, {@link * Pattern#MULTILINE}, {@link Pattern#DOTALL}, {@link Pattern#UNICODE_CASE}, {@link * Pattern#CANON_EQ}, {@link Pattern#UNIX_LINES}, {@link Pattern#LITERAL} and {@link * Pattern#COMMENTS} * @throws IllegalArgumentException If bit values other than those corresponding to the defined * match flags are set in flags * @throws PatternSyntaxException If the expression's syntax is invalid */ public static Pattern regexp(String regex, int flags) { return Strings.regexp(regex, flags); } /** * This is synonym for "regexp". * * @see #regexp(String, int) */ public static Pattern pattern(String regex, int flags) { return Strings.pattern(regex, flags); } /** * Matches the given (precompiled) regular expression and attempts to match the given input * against it. */ public static boolean matches(Pattern regex, String input) { return Strings.matches(regex, input); } /** * Compiles the given regular expression and attempts to match the given input against it. * *

An invocation of this convenience method of the form * *

* *
   * Pattern.matches(regex, input);
* *
* *

behaves in exactly the same way as the expression * *

* *
   * Pattern.compile(regex).matcher(input).matches()
* *
* *

If a pattern is to be used multiple times, compiling it once and reusing it will be more * efficient than invoking this method each time. * * @param regex The expression to be compiled * @param input The character sequence to be matched * @throws PatternSyntaxException If the expression's syntax is invalid */ public static boolean matches(String regex, String input) { return Strings.matches(regex, input); } // Numbers utilities /** * Returns a double value with a positive sign, greater than or equal to 0.0 * and less than 1.0. Returned values are chosen pseudorandomly with * (approximately) uniform distribution from that range. */ public static double random() { return Numbers.random(); } /** * Returns the natural logarithm (base e) of a double value. Special cases: * *

    *
  • If the argument is NaN or less than zero, then the result is NaN. *
  • If the argument is positive infinity, then the result is positive infinity. *
  • If the argument is positive zero or negative zero, then the result is negative infinity. *
* *

The computed result must be within 1 ulp of the exact result. Results must be * semi-monotonic. * * @param a a value * @return the value ln a, the natural logarithm of a. */ public static strictfp double log(double a) { return Numbers.log(a); } /** * Returns the base 10 logarithm of a double value. Special cases: * *

    *
  • If the argument is NaN or less than zero, then the result is NaN. *
  • If the argument is positive infinity, then the result is positive infinity. *
  • If the argument is positive zero or negative zero, then the result is negative infinity. *
  • If the argument is equal to 10n for integer n, then the result * is n. *
* *

The computed result must be within 1 ulp of the exact result. Results must be * semi-monotonic. * * @param a a value * @return the base 10 logarithm of a. */ public static strictfp double log10(double a) { return Numbers.log10(a); } /** * Returns Euler's number e raised to the power of a double value. Special * cases: * *

    *
  • If the argument is NaN, the result is NaN. *
  • If the argument is positive infinity, then the result is positive infinity. *
  • If the argument is negative infinity, then the result is positive zero. *
* *

The computed result must be within 1 ulp of the exact result. Results must be * semi-monotonic. * * @param a the exponent to raise e to. * @return the value ea, where e is the base of the natural * logarithms. */ public static strictfp double exp(double a) { return Numbers.exp(a); } /** * Returns true if the specified number is a Not-a-Number (NaN) value, false * otherwise. * * @param d the value to be tested. * @return true if the value of the argument is NaN; false otherwise. */ public static boolean isNaN(double d) { return Numbers.isNaN(d); } /** * Returns true if the specified number is a Not-a-Number (NaN) value, false * otherwise. * * @param f the value to be tested. * @return true if the value of the argument is NaN; false otherwise. */ public static boolean isNaN(float f) { return Numbers.isNaN(f); } /** * Returns true if the specified number is infinitely large in magnitude, false * otherwise. * * @param d the value to be tested. * @return true if the value of the argument is positive infinity or negative * infinity; false otherwise. */ public static boolean isInfinite(double d) { return Numbers.isInfinite(d); } /** * Returns true if the specified number is infinitely large in magnitude, false * otherwise. * * @param f the value to be tested. * @return true if the value of the argument is positive infinity or negative * infinity; false otherwise. */ public static boolean isInfinite(float f) { return Numbers.isInfinite(f); } // string parsing methods /** * Parses the string argument as a boolean. The boolean returned represents the value * true if the string argument is not null and is equal, ignoring case, * to the string {@code "true"}. * *

Example: {@code Boolean.parseBoolean("True")} returns true.
* Example: {@code Boolean.parseBoolean("yes")} returns false. * * @param s the String containing the boolean representation to be parsed * @return the boolean represented by the string argument */ public static boolean parseBoolean(String s) { return Numbers.parseBoolean(s); } /** * Parses the string argument as a signed decimal byte. The characters in the string * must all be decimal digits, except that the first character may be an ASCII minus sign * '-' ('\u002D') to indicate a negative value. The resulting byte * value is returned. * * @param s a String containing the byte representation to be parsed * @return the byte value represented by the argument in decimal */ public static byte parseByte(String s) { return Numbers.parseByte(s); } /** * Parses the string argument as a signed decimal short. The characters in the string * must all be decimal digits, except that the first character may be an ASCII minus sign * '-' ('\u002D') to indicate a negative value. The resulting short * value is returned. * * @param s a String containing the short representation to be parsed * @return the short value represented by the argument in decimal. */ public static short parseShort(String s) { return Numbers.parseShort(s); } /** * Parses the string argument as a signed decimal integer. The characters in the string must all * be decimal digits, except that the first character may be an ASCII minus sign '-' * ('\u002D') to indicate a negative value. The resulting integer value is * returned. * * @param s a String containing the int representation to be parsed * @return the integer value represented by the argument in decimal. */ public static int parseInt(String s) { return Numbers.parseInt(s); } /** * Parses the string argument as a signed decimal long. The characters in the string * must all be decimal digits, except that the first character may be an ASCII minus sign * '-' (\u002D') to indicate a negative value. The resulting long * value is returned. * *

Note that neither the character L ('\u004C') nor l * ('\u006C') is permitted to appear at the end of the string as a type * indicator, as would be permitted in Java programming language source code. * * @param s a String containing the long representation to be parsed * @return the long represented by the argument in decimal. */ public static long parseLong(String s) { return Numbers.parseLong(s); } /** * Returns a new float initialized to the value represented by the specified * String, as performed by the valueOf method of class Float. * * @param s the string to be parsed. * @return the float value represented by the string argument. */ public static float parseFloat(String s) { return Numbers.parseFloat(s); } /** * Returns a new double initialized to the value represented by the specified * String, as performed by the valueOf methcod of class Double. * * @param s the string to be parsed. * @return the double value represented by the string argument. */ public static double parseDouble(String s) { return Numbers.parseDouble(s); } // boxing methods /** * Returns a Boolean instance representing the specified boolean value. If the * specified boolean value is true, this method returns Boolean.TRUE; * if it is false, this method returns Boolean.FALSE. * * @param b a boolean value. * @return a Boolean instance representing b. */ public static Boolean box(boolean b) { return Numbers.box(b); } /** * Returns a Character instance representing the specified char value. * * @param c a char value. * @return a Character instance representing c. */ public static Character box(char c) { return Numbers.box(c); } /** * Returns a Byte instance representing the specified byte value. * * @param b a byte value. * @return a Byte instance representing b. */ public static Byte box(byte b) { return Numbers.box(b); } /** * Returns a Short instance representing the specified short value. * * @param s a short value. * @return a Short instance representing s. */ public static Short box(short s) { return Numbers.box(s); } /** * Returns a Integer instance representing the specified int value. * * @param i an int value. * @return a Integer instance representing i. */ public static Integer box(int i) { return Numbers.box(i); } /** * Returns a Long instance representing the specified long value. * * @param l a long value. * @return a Long instance representing l. */ public static Long box(long l) { return Numbers.box(l); } /** * Returns a Float instance representing the specified float value. * * @param f a float value. * @return a Float instance representing f. */ public static Float box(float f) { return Numbers.box(f); } /** * Returns a Double instance representing the specified double value. * * @param d a double value. * @return a Double instance representing d. */ public static Double box(double d) { return Numbers.box(d); } // unboxing methods /** * Returns the value of the given Boolean object as a boolean primitive. * * @param b the Boolean object whose value is returned. * @return the primitive boolean value of the object. */ public static boolean unbox(Boolean b) { return Numbers.unbox(b); } /** * Returns the value of the given Character object as a char primitive. * * @param ch the Character object whose value is returned. * @return the primitive char value of the object. */ public static char unbox(Character ch) { return Numbers.unbox(ch); } /** * Returns the value of the specified Byte as a byte. * * @param b Byte that is unboxed * @return the byte value represented by the Byte. */ public static byte unbox(Byte b) { return Numbers.unbox(b); } /** * Returns the short value represented by Short. * * @param s Short that is unboxed. * @return the short value represented by the Short. */ public static short unbox(Short s) { return Numbers.unbox(s); } /** * Returns the value of represented by Integer. * * @param i Integer that is unboxed. * @return the int value represented by the Integer. */ public static int unbox(Integer i) { return Numbers.unbox(i); } /** * Returns the long value represented by the specified Long. * * @param l Long to be unboxed. * @return the long value represented by the Long. */ public static long unbox(Long l) { return Numbers.unbox(l); } /** * Returns the float value represented by the specified Float. * * @param f Float to be unboxed. * @return the float value represented by the Float. */ public static float unbox(Float f) { return Numbers.unbox(f); } /** * Returns the double value represented by the specified Double. * * @param d Double to be unboxed. */ public static double unbox(Double d) { return Numbers.unbox(d); } // primitive types to String conversion /** * Returns a String object representing the specified boolean. If the specified boolean * is true, then the string {@code "true"} will be returned, otherwise the string * {@code "false"} will be returned. * * @param b the boolean to be converted * @return the string representation of the specified boolean */ public static String str(boolean b) { return Strings.str(b); } /** * Returns a String object representing the specified char. The result * is a string of length 1 consisting solely of the specified char. * * @param c the char to be converted * @return the string representation of the specified char */ public static String str(char c) { return Strings.str(c); } /** * Returns a String object representing the specified integer. The argument is * converted to signed decimal representation and returned as a string. * * @param i an integer to be converted. * @return a string representation of the argument in base 10. */ public static String str(int i) { return Strings.str(i); } /** * Returns a string representation of the integer argument as an unsigned integer in base 16. * *

The unsigned integer value is the argument plus 232 if the argument is negative; * otherwise, it is equal to the argument. This value is converted to a string of ASCII digits in * hexadecimal (base 16) with no extra leading 0s. If the unsigned magnitude is * zero, it is represented by a single zero character '0' ('\u0030' * ); otherwise, the first character of the representation of the unsigned magnitude will not be * the zero character. The following characters are used as hexadecimal digits: * *

* *
   * 0123456789abcdef
   * 
* *
* * These are the characters '\u0030' through '\u0039' and * '\u0061' through '\u0066'. * * @param i an integer to be converted to a string. * @return the string representation of the unsigned integer value represented by the argument in * hexadecimal (base 16). */ public static String toHexString(int i) { return Strings.toHexString(i); } /** * Returns a String object representing the specified long. The argument * is converted to signed decimal representation and returned as a string. * * @param l a long to be converted. * @return a string representation of the argument in base 10. */ public static String str(long l) { return Strings.str(l); } /** * Returns a string representation of the long argument as an unsigned integer in * base 16. * *

The unsigned long value is the argument plus 264 if the argument is * negative; otherwise, it is equal to the argument. This value is converted to a string of ASCII * digits in hexadecimal (base 16) with no extra leading 0s. If the unsigned * magnitude is zero, it is represented by a single zero character '0' ( * '\u0030'); otherwise, the first character of the representation of the unsigned * magnitude will not be the zero character. The following characters are used as hexadecimal * digits: * *

* *
   * 0123456789abcdef
   * 
* *
* * These are the characters '\u0030' through '\u0039' and * '\u0061' through '\u0066'. * * @param l a long to be converted to a string. * @return the string representation of the unsigned long value represented by the * argument in hexadecimal (base 16). */ public static String toHexString(long l) { return Strings.toHexString(l); } /** * Returns a string representation of the float argument. All characters mentioned * below are ASCII characters. * *
    *
  • If the argument is NaN, the result is the string "NaN". *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute value) * of the argument. If the sign is negative, the first character of the result is '- * ' ('\u002D'); if the sign is positive, no sign character appears * in the result. As for the magnitude m: *
      *
    • If m is infinity, it is represented by the characters "Infinity" * ; thus, positive infinity produces the result "Infinity" and * negative infinity produces the result "-Infinity". *
    • If m is zero, it is represented by the characters "0.0"; thus, * negative zero produces the result "-0.0" and positive zero produces * the result "0.0". *
    • If m is greater than or equal to 10-3 but less than * 107, then it is represented as the integer part of m, in decimal * form with no leading zeroes, followed by '.' ('\u002E' * ), followed by one or more decimal digits representing the fractional part * of m. *
    • If m is less than 10-3 or greater than or equal to * 107, then it is represented in so-called "computerized scientific * notation." Let n be the unique integer such that 10n <= * m < 10n+1; then let a be the mathematically * exact quotient of m and 10n so that 1 <= a < * 10. The magnitude is then represented as the integer part of a, as a single * decimal digit, followed by '.' ('\u002E'), followed * by decimal digits representing the fractional part of a, followed by the * letter 'E' ('\u0045'), followed by a representation * of n as a decimal integer, as produced by the method {@link * java.lang.Integer#toString(int)}. *
    *
* * How many digits must be printed for the fractional part of m or a? There must be * at least one digit to represent the fractional part, and beyond that as many, but only as many, * more digits as are needed to uniquely distinguish the argument value from adjacent values of * type float. That is, suppose that x is the exact mathematical value * represented by the decimal representation produced by this method for a finite nonzero argument * f. Then f must be the float value nearest to x; or, if two * float values are equally close to x, then f must be one of them and * the least significant bit of the significand of f must be 0. * *

* * @param f the float to be converted. * @return a string representation of the argument. */ public static String str(float f) { return Strings.str(f); } /** * Returns a string representation of the double argument. All characters mentioned * below are ASCII characters. * *

    *
  • If the argument is NaN, the result is the string "NaN". *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute value) * of the argument. If the sign is negative, the first character of the result is '- * ' ('\u002D'); if the sign is positive, no sign character appears * in the result. As for the magnitude m: *
      *
    • If m is infinity, it is represented by the characters "Infinity" * ; thus, positive infinity produces the result "Infinity" and * negative infinity produces the result "-Infinity". *
    • If m is zero, it is represented by the characters "0.0"; thus, * negative zero produces the result "-0.0" and positive zero produces * the result "0.0". *
    • If m is greater than or equal to 10-3 but less than * 107, then it is represented as the integer part of m, in decimal * form with no leading zeroes, followed by '.' ('\u002E' * ), followed by one or more decimal digits representing the fractional part * of m. *
    • If m is less than 10-3 or greater than or equal to * 107, then it is represented in so-called "computerized scientific * notation." Let n be the unique integer such that 10n <= * m < 10n+1; then let a be the mathematically * exact quotient of m and 10n so that 1 <= a < * 10. The magnitude is then represented as the integer part of a, as a single * decimal digit, followed by '.' ('\u002E'), followed * by decimal digits representing the fractional part of a, followed by the * letter 'E' ('\u0045'), followed by a representation * of n as a decimal integer, as produced by the method {@link * Integer#toString(int)}. *
    *
* * How many digits must be printed for the fractional part of m or a? There must be * at least one digit to represent the fractional part, and beyond that as many, but only as many, * more digits as are needed to uniquely distinguish the argument value from adjacent values of * type double. That is, suppose that x is the exact mathematical value * represented by the decimal representation produced by this method for a finite nonzero argument * d. Then d must be the double value nearest to x; or if two * double values are equally close to x, then d must be one of them and * the least significant bit of the significand of d must be 0. * *

* * @param d the double to be converted. * @return a string representation of the argument. */ public static String str(double d) { return Strings.str(d); } /** * Exits the BTrace session -- note that the particular client's tracing session exits and not the * observed/traced program! After exit call, the trace action method terminates immediately and no * other probe action method (of that client) will be called after that. * * @param exitCode exit value sent to the client */ public static void exit(int exitCode) { Sys.exit(exitCode); } /** * This is same as exit(int) except that the exit code is zero. * * @see #exit(int) */ public static void exit() { Sys.exit(); } /** accessing jvmstat (perf) int counter */ public static long perfInt(String name) { return Counters.perfInt(name); } /** accessing jvmstat (perf) long counter */ public static long perfLong(String name) { return Counters.perfLong(name); } /** accessing jvmstat (perf) String counter */ public static String perfString(String name) { return Counters.perfString(name); } /** Operating on maps */ // Create a new map public static Map newHashMap() { return Collections.newHashMap(); } public static Map newWeakMap() { return Collections.newWeakMap(); } public static Deque newDeque() { return Collections.newDeque(); } // get a particular item from a Map public static V get(Map map, K key) { return Collections.get(map, key); } // check whether an item exists public static boolean containsKey(Map map, K key) { return Collections.containsKey(map, key); } public static boolean containsValue(Map map, V value) { return Collections.containsValue(map, value); } // put a particular item into a Map public static V put(Map map, K key, V value) { return Collections.put(map, key, value); } // remove a particular item from a Map public static V remove(Map map, K key) { return Collections.remove(map, key); } // clear all items from a Map public static void clear(Map map) { Collections.clear(map); } // return the size of a Map public static int size(Map map) { return Collections.size(map); } public static boolean isEmpty(Map map) { return Collections.isEmpty(map); } // operations on collections public static int size(Collection coll) { return Collections.size(coll); } public static boolean isEmpty(Collection coll) { return Collections.isEmpty(coll); } public static boolean contains(Collection coll, Object obj) { return Collections.contains(coll, obj); } public static boolean contains(Object[] array, Object value) { return Collections.contains(array, value); } // operations on Deque public static void push(Deque queue, V value) { Collections.push(queue, value); } public static V poll(Deque queue) { return Collections.poll(queue); } public static V peek(Deque queue) { return Collections.peek(queue); } public static void addLast(Deque queue, V value) { Collections.addLast(queue, value); } public static V peekFirst(Deque queue) { return Collections.peekFirst(queue); } public static V peekLast(Deque queue) { return Collections.peekLast(queue); } public static V removeLast(Deque queue) { return Collections.removeLast(queue); } public static V removeFirst(Deque queue) { return Collections.removeFirst(queue); } /** * Returns the current instrumentation level. * *

Instrumentation level is used in evaluating {@linkplain OnMethod#enableAt()} expressions to * enable/disable the probe handler. * * @return the instrumentation level (non negative integer) * @since 1.3.4 */ public static int getInstrumentationLevel() { return BTraceRuntime.getInstrumentationLevel(); } /** * Sets the current instrumentation level. * *

Instrumentation level is used in evaluating {@linkplain OnMethod#enableAt()} expressions to * enable/disable the probe handler. * * @param level an arbitrary non negative integer number * @since 1.3.4 */ public static void setInstrumentationLevel(int level) { if (level >= 0) { BTraceRuntime.setInstrumentationLevel(level); } } /** * Returns n'th command line argument. null if not available. * * @param n command line argument index * @return n'th command line argument */ public static String $(int n) { return Sys.$(n); } /** Returns the process id of the currently BTrace'd process. */ public static int getpid() { return Sys.getpid(); } /** Returns the number of command line arguments. */ public static int $length() { return Sys.$length(); } // atomic stuff /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public static AtomicInteger newAtomicInteger(int initialValue) { return Atomic.newAtomicInteger(initialValue); } /** * Gets the current value of the given AtomicInteger. * * @param ai AtomicInteger whose value is returned. * @return the current value */ public static int get(AtomicInteger ai) { return Atomic.get(ai); } /** * Sets to the given value to the given AtomicInteger. * * @param ai AtomicInteger whose value is set. * @param newValue the new value */ public static void set(AtomicInteger ai, int newValue) { Atomic.set(ai, newValue); } /** * Eventually sets to the given value to the given AtomicInteger. * * @param ai AtomicInteger whose value is lazily set. * @param newValue the new value */ public static void lazySet(AtomicInteger ai, int newValue) { Atomic.lazySet(ai, newValue); } /** * Atomically sets the value of given AtomitInteger to the given updated value if the current * value {@code ==} the expected value. * * @param ai AtomicInteger whose value is compared and set. * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that the actual value was not equal to the * expected value. */ public static boolean compareAndSet(AtomicInteger ai, int expect, int update) { return Atomic.compareAndSet(ai, expect, update); } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. * *

May fail spuriously and does not provide * ordering guarantees, so is only rarely an appropriate alternative to {@code compareAndSet}. * * @param ai AtomicInteger whose value is weakly compared and set. * @param expect the expected value * @param update the new value * @return true if successful. */ public static boolean weakCompareAndSet(AtomicInteger ai, int expect, int update) { return Atomic.weakCompareAndSet(ai, expect, update); } /** * Atomically increments by one the current value of given AtomicInteger. * * @param ai AtomicInteger that is incremented. * @return the previous value */ public static int getAndIncrement(AtomicInteger ai) { return Atomic.getAndIncrement(ai); } /** * Atomically decrements by one the current value of given AtomicInteger. * * @param ai AtomicInteger that is decremented. * @return the previous value */ public static int getAndDecrement(AtomicInteger ai) { return Atomic.getAndDecrement(ai); } /** * Atomically increments by one the current value of given AtomicInteger. * * @param ai AtomicInteger that is incremented. * @return the updated value */ public static int incrementAndGet(AtomicInteger ai) { return Atomic.incrementAndGet(ai); } /** * Atomically decrements by one the current value of given AtomicInteger. * * @param ai AtomicInteger whose value is decremented. * @return the updated value */ public static int decrementAndGet(AtomicInteger ai) { return Atomic.decrementAndGet(ai); } /** * Atomically adds the given value to the current value. * * @param ai AtomicInteger whose value is added to. * @param delta the value to add * @return the previous value */ public static int getAndAdd(AtomicInteger ai, int delta) { return Atomic.getAndAdd(ai, delta); } /** * Atomically adds the given value to the current value. * * @param ai AtomicInteger whose value is added to. * @param delta the value to add * @return the updated value */ public static int addAndGet(AtomicInteger ai, int delta) { return Atomic.addAndGet(ai, delta); } /** * Atomically sets to the given value and returns the old value. * * @param ai AtomicInteger whose value is set. * @param newValue the new value * @return the previous value */ public static int getAndSet(AtomicInteger ai, int newValue) { return Atomic.getAndSet(ai, newValue); } /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public static AtomicLong newAtomicLong(long initialValue) { return Atomic.newAtomicLong(initialValue); } /** * Gets the current value the given AtomicLong. * * @param al AtomicLong whose value is returned. * @return the current value */ public static long get(AtomicLong al) { return Atomic.get(al); } /** * Sets to the given value. * * @param al AtomicLong whose value is set. * @param newValue the new value */ public static void set(AtomicLong al, long newValue) { Atomic.set(al, newValue); } /** * Eventually sets to the given value to the given AtomicLong. * * @param al AtomicLong whose value is set. * @param newValue the new value */ public static void lazySet(AtomicLong al, long newValue) { Atomic.lazySet(al, newValue); } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. * * @param al AtomicLong whose value is compared and set. * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that the actual value was not equal to the * expected value. */ public static boolean compareAndSet(AtomicLong al, long expect, long update) { return Atomic.compareAndSet(al, expect, update); } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. * *

May fail spuriously and does not provide ordering guarantees, so is only rarely an * appropriate alternative to {@code compareAndSet}. * * @param al AtomicLong whose value is compared and set. * @param expect the expected value * @param update the new value * @return true if successful. */ public static boolean weakCompareAndSet(AtomicLong al, long expect, long update) { return Atomic.weakCompareAndSet(al, expect, update); } /** * Atomically increments by one the current value. * * @param al AtomicLong whose value is incremented. * @return the previous value */ public static long getAndIncrement(AtomicLong al) { return Atomic.getAndIncrement(al); } /** * Atomically decrements by one the current value. * * @param al AtomicLong whose value is decremented. * @return the previous value */ public static long getAndDecrement(AtomicLong al) { return Atomic.getAndDecrement(al); } /** * Atomically increments by one the current value. * * @param al AtomicLong whose value is incremented. * @return the updated value */ public static long incrementAndGet(AtomicLong al) { return Atomic.incrementAndGet(al); } /** * Atomically decrements by one the current value. * * @param al AtomicLong whose value is decremented. * @return the updated value */ public static long decrementAndGet(AtomicLong al) { return Atomic.decrementAndGet(al); } /** * Atomically adds the given value to the current value. * * @param al AtomicLong whose value is added to. * @param delta the value to add * @return the previous value */ public static long getAndAdd(AtomicLong al, long delta) { return Atomic.getAndAdd(al, delta); } /** * Atomically adds the given value to the current value. * * @param al AtomicLong whose value is added to * @param delta the value to add * @return the updated value */ public static long addAndGet(AtomicLong al, long delta) { return Atomic.addAndGet(al, delta); } /** * Atomically sets to the given value and returns the old value. * * @param al AtomicLong that is set. * @param newValue the new value * @return the previous value */ public static long getAndSet(AtomicLong al, long newValue) { return Atomic.getAndSet(al, newValue); } /** * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. * * @see #dtraceProbe(String, String, int, int) */ public static int dtraceProbe(String str1, String str2) { return D.probe(str1, str2); } /** * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. * * @see #dtraceProbe(String, String, int, int) */ public static int dtraceProbe(String str1, String str2, int i1) { return D.probe(str1, str2, i1); } /** * BTrace to DTrace communication channel. Raise DTrace USDT probe from BTrace. * * @param str1 first String param to DTrace probe * @param str2 second String param to DTrace probe * @param i1 first int param to DTrace probe * @param i2 second int param to DTrace probe */ public static int dtraceProbe(String str1, String str2, int i1, int i2) { return D.probe(str1, str2, i1, i2); } /** * Gets the system property indicated by the specified key. * * @param key the name of the system property. * @return the string value of the system property, or null if there is no property * with that key. * @throws NullPointerException if key is null. * @throws IllegalArgumentException if key is empty. */ public static String property(String key) { return Sys.Env.property(key); } /** * Returns all Sys properties. * * @return the system properties */ public static Properties properties() { return Sys.Env.properties(); } /** Prints all Sys properties. */ public static void printProperties() { Sys.Env.printProperties(); } /** * Gets the value of the specified environment variable. An environment variable is a * system-dependent external named value. * * @param name the name of the environment variable * @return the string value of the variable, or null if the variable is not defined * in the system environment * @throws NullPointerException if name is null */ public static String getenv(String name) { return Sys.Env.getenv(name); } /** * Returns an unmodifiable string map view of the current system environment. The environment is a * system-dependent mapping from names to values which is passed from parent to child processes. * * @return the environment as a map of variable names to values */ public static Map getenv() { return Sys.Env.getenv(); } /** Prints all system environment values. */ public static void printEnv() { Sys.Env.printEnv(); } /** * Returns the number of processors available to the Java virtual machine. * *

This value may change during a particular invocation of the virtual machine. Applications * that are sensitive to the number of available processors should therefore occasionally poll * this property and adjust their resource usage appropriately. * * @return the maximum number of processors available to the virtual machine; never smaller than * one */ public static long availableProcessors() { return Sys.Env.availableProcessors(); } // memory usage /** * Returns the amount of free memory in the Java Virtual Machine. Calling the gc * method may result in increasing the value returned by freeMemory. * * @return an approximation to the total amount of memory currently available for future allocated * objects, measured in bytes. */ public static long freeMemory() { return Sys.Memory.freeMemory(); } /** * Returns the total amount of memory in the Java virtual machine. The value returned by this * method may vary over time, depending on the host environment. * *

Note that the amount of memory required to hold an object of any given type may be * implementation-dependent. * * @return the total amount of memory currently available for current and future objects, measured * in bytes. */ public static long totalMemory() { return Sys.Memory.totalMemory(); } /** * Returns the maximum amount of memory that the Java virtual machine will attempt to use. If * there is no inherent limit then the value {@link java.lang.Long#MAX_VALUE} will be returned. * * @return the maximum amount of memory that the virtual machine will attempt to use, measured in * bytes */ public static long maxMemory() { return Sys.Memory.maxMemory(); } /** Returns heap memory usage */ public static MemoryUsage heapUsage() { return Sys.Memory.heapUsage(); } /** Returns non-heap memory usage */ public static MemoryUsage nonHeapUsage() { return Sys.Memory.nonHeapUsage(); } /** * Returns the amount of memory in bytes that the Java virtual machine initially requests from the * operating system for memory management. */ public static long init(MemoryUsage mu) { return Sys.Memory.init(mu); } /** * Returns the amount of memory in bytes that is committed for the Java virtual machine to use. * This amount of memory is guaranteed for the Java virtual machine to use. */ public static long committed(MemoryUsage mu) { return Sys.Memory.committed(mu); } /** * Returns the maximum amount of memory in bytes that can be used for memory management. This * method returns -1 if the maximum memory size is undefined. */ public static long max(MemoryUsage mu) { return Sys.Memory.max(mu); } /** Returns the amount of used memory in bytes. */ public static long used(MemoryUsage mu) { return Sys.Memory.used(mu); } /** Returns the approximate number of objects for which finalization is pending. */ public static long finalizationCount() { return Sys.Memory.finalizationCount(); } /** * Returns the input arguments passed to the Java virtual machine which does not include the * arguments to the main method. This method returns an empty list if there is no input * argument to the Java virtual machine. * *

Some Java virtual machine implementations may take input arguments from multiple different * sources: for examples, arguments passed from the application that launches the Java virtual * machine such as the 'java' command, environment variables, configuration files, etc. * *

Typically, not all command-line options to the 'java' command are passed to the Java virtual * machine. Thus, the returned input arguments may not include all command-line options. * * @return a list of String objects; each element is an argument passed to the Java * virtual machine. */ public static List vmArguments() { return Sys.VM.vmArguments(); } /** * Prints VM input arguments list. * * @see #vmArguments */ public static void printVmArguments() { Sys.VM.printVmArguments(); } /** * Returns the Java virtual machine implementation version. This method is equivalent to * Sys.getProperty("java.vm.version"). * * @return the Java virtual machine implementation version. */ public static String vmVersion() { return Sys.VM.vmVersion(); } /** * Tests if the Java virtual machine supports the boot class path mechanism used by the bootstrap * class loader to search for class files. * * @return true if the Java virtual machine supports the class path mechanism; * false otherwise. */ public static boolean isBootClassPathSupported() { return Sys.VM.isBootClassPathSupported(); } /** * Returns the boot class path that is used by the bootstrap class loader to search for class * files. * *

Multiple paths in the boot class path are separated by the path separator character of the * platform on which the Java virtual machine is running. * *

A Java virtual machine implementation may not support the boot class path mechanism for the * bootstrap class loader to search for class files. The {@link #isBootClassPathSupported} method * can be used to determine if the Java virtual machine supports this method. * * @return the boot class path. * @throws java.lang.UnsupportedOperationException if the Java virtual machine does not support * this operation. */ public static String bootClassPath() { return Sys.VM.bootClassPath(); } /** * Returns the Java class path that is used by the system class loader to search for class files. * This method is equivalent to Sys.getProperty("java.class.path"). * * @return the Java class path. */ public static String classPath() { return Sys.VM.classPath(); } /** * Returns the Java library path. This method is equivalent to * Sys.getProperty("java.library.path"). * *

Multiple paths in the Java library path are separated by the path separator character of the * platform of the Java virtual machine being monitored. * * @return the Java library path. */ public static String libraryPath() { return Sys.VM.libraryPath(); } /** * Returns the current number of live threads including both daemon and non-daemon threads. * * @return the current number of live threads. */ public static long threadCount() { return Sys.VM.threadCount(); } /** * Returns the peak live thread count since the Java virtual machine started or peak was reset. * * @return the peak live thread count. */ public static long peakThreadCount() { return Sys.VM.peakThreadCount(); } /** * Returns the total number of threads created and also started since the Java virtual machine * started. * * @return the total number of threads started. */ public static long totalStartedThreadCount() { return Sys.VM.totalStartedThreadCount(); } /** * Returns the current number of live daemon threads. * * @return the current number of live daemon threads. */ public static long daemonThreadCount() { return Sys.VM.daemonThreadCount(); } /** * Returns the total CPU time for the current thread in nanoseconds. The returned value is of * nanoseconds precision but not necessarily nanoseconds accuracy. If the implementation * distinguishes between user mode time and system mode time, the returned CPU time is the amount * of time that the current thread has executed in user mode or system mode. */ public static long currentThreadCpuTime() { return Sys.VM.currentThreadCpuTime(); } /** * Returns the CPU time that the current thread has executed in user mode in nanoseconds. The * returned value is of nanoseconds precision but not necessarily nanoseconds accuracy. */ public static long currentThreadUserTime() { return Sys.VM.currentThreadUserTime(); } /** * Returns the total amount of time spent in GarbageCollection up to this point since the * application was started. * * @return Returns the amount of overall time spent in GC */ public static long getTotalGcTime() { return Sys.Memory.getTotalGcTime(); } /** * Returns an implementation-specific approximation of the amount of storage consumed by the * specified object. The result may include some or all of the object's overhead, and thus is * useful for comparison within an implementation but not between implementations. * *

The estimate may change during a single invocation of the JVM. * * @param objectToSize the object to size * @return an implementation-specific approximation of the amount of storage consumed by the * specified object * @throws java.lang.NullPointerException if the supplied Object is null. */ public static long sizeof(Object objectToSize) { return BTraceRuntime.sizeof(objectToSize); } /** * Dump the snapshot of the Java heap to a file in hprof binary format. Only the live objects are * dumped. Under the current dir of traced app, ./btrace<pid>/btrace-class/ directory is * created. Under that directory, a file of given fileName is created. * * @param fileName name of the file to which heap is dumped */ public static void dumpHeap(String fileName) { Sys.Memory.dumpHeap(fileName); } /** * Dump the snapshot of the Java heap to a file in hprof binary format. Under the current dir of * traced app, ./btrace<pid>/btrace-class/ directory is created. Under that directory, a * file of given fileName is created. * * @param fileName name of the file to which heap is dumped * @param live flag that tells whether only live objects are to be dumped or all objects are to be * dumped. */ public static void dumpHeap(String fileName, boolean live) { Sys.Memory.dumpHeap(fileName, live); } /** * Runs the garbage collector. * *

Calling the gc method suggests that the Java Virtual Machine expend effort * toward recycling unused objects in order to make the memory they currently occupy available for * quick reuse. When control returns from the method call, the Java Virtual Machine has made a * best effort to reclaim space from all discarded objects. This method calls Sys.gc() to perform * GC. */ public static void gc() { Sys.Memory.gc(); } /** * Runs the finalization methods of any objects pending finalization. * *

Calling this method suggests that the Java Virtual Machine expend effort toward running the * finalize methods of objects that have been found to be discarded but whose * finalize methods have not yet been run. When control returns from the method call, the * Java Virtual Machine has made a best effort to complete all outstanding finalizations. This * method calls Sys.runFinalization() to run finalization. */ public static void runFinalization() { Sys.Memory.runFinalization(); } /** * Serialize a given object into the given file. Under the current dir of traced app, * ./btrace<pid>/btrace-class/ directory is created. Under that directory, a file of given * fileName is created. * * @param obj object that has to be serialized. * @param fileName name of the file to which the object is serialized. */ public static void serialize(Serializable obj, String fileName) { Export.serialize(obj, fileName); } /** * Creates an XML document to persist the tree of the all transitively reachable objects from * given "root" object. */ public static String toXML(Object obj) { return Export.toXML(obj); } /** * Writes an XML document to persist the tree of the all the transitively reachable objects from * the given "root" object. Under the current dir of traced app, ./btrace<pid>/btrace-class/ * directory is created. Under that directory, a file of the given fileName is created. */ public static void writeXML(Object obj, String fileName) { Export.writeXML(obj, fileName); } /** * Writes a .dot document to persist the tree of the all the transitively reachable objects from * the given "root" object. .dot documents can be viewed by Graphviz application * (www.graphviz.org) Under the current dir of traced app, ./btrace<pid>/btrace-class/ * directory is created. Under that directory, a file of the given fileName is created. * * @since 1.1 */ public static void writeDOT(Object obj, String fileName) { Export.writeDOT(obj, fileName); } // speculative buffer management /** * Returns an identifier for a new speculative buffer. * * @return new speculative buffer id */ public static int speculation() { return Speculation.speculation(); } /** * Sets current speculative buffer id. * * @param id the speculative buffer id */ public static void speculate(int id) { Speculation.speculate(id); } /** * Commits the speculative buffer associated with id. * * @param id the speculative buffer id */ public static void commit(int id) { Speculation.commit(id); } /** * Discards the speculative buffer associated with id. * * @param id the speculative buffer id */ public static void discard(int id) { Speculation.discard(id); } // Internals only below this point private static void checkStatic(Field field) { if (!Modifier.isStatic(field.getModifiers())) { throw new IllegalArgumentException(field.getName() + " is not a static field"); } } private static Field getField(Class clazz, String name, boolean throwError) { return AccessController.doPrivileged( (PrivilegedAction) () -> { Field field = null; Class cClass = clazz; try { while (Objects.isNull(field) && Objects.nonNull(cClass)) { try { field = cClass.getDeclaredField(name); } catch (NoSuchFieldException exp) { // Ignore the exception and continue looking for the parent class cClass = cClass.getSuperclass(); } } if (Objects.isNull(cClass)) { throw new NoSuchFieldException(name); } field.setAccessible(true); return field; } catch (Exception exp) { if (throwError) { throw translate(exp); } else { return null; } } }); } private static Field[] getAllFields(Class clazz) { return AccessController.doPrivileged( (PrivilegedAction) () -> { try { Field[] fields = clazz.getDeclaredFields(); for (Field f : fields) { f.setAccessible(true); } return fields; } catch (Exception exp) { throw translate(exp); } }); } private static void addFieldValues( StringBuilder buf, Object obj, Class clazz, boolean classNamePrefix) { Field[] fields = getAllFields(clazz); for (Field f : fields) { int modifiers = f.getModifiers(); if (!Modifier.isStatic(modifiers)) { if (classNamePrefix) { buf.append(f.getDeclaringClass().getName()); buf.append('.'); } buf.append(f.getName()); buf.append('='); try { buf.append(Strings.str(f.get(obj))); } catch (Exception exp) { throw translate(exp); } buf.append(", "); } } Class sc = clazz.getSuperclass(); if (sc != null) { addFieldValues(buf, obj, sc, classNamePrefix); } } private static void addStaticFieldValues( StringBuilder buf, Class clazz, boolean classNamePrefix) { Field[] fields = getAllFields(clazz); for (Field f : fields) { int modifiers = f.getModifiers(); if (Modifier.isStatic(modifiers)) { if (classNamePrefix) { buf.append(f.getDeclaringClass().getName()); buf.append('.'); } buf.append(f.getName()); buf.append('='); try { buf.append(Strings.str(f.get(null))); } catch (Exception exp) { throw translate(exp); } buf.append(", "); } } Class sc = clazz.getSuperclass(); if (sc != null) { addStaticFieldValues(buf, sc, classNamePrefix); } } private static RuntimeException translate(Exception exp) { if (exp instanceof RuntimeException) { return (RuntimeException) exp; } else { return new RuntimeException(exp); } } /** ******** Namespaced methods ***************** */ /* * Wraps the threads related BTrace utility methods * @since 1.2 */ public static class Threads { // Thread and stack access /** * Tests whether this thread has been interrupted. The interrupted status of the thread * is unaffected by this method. * *

A thread interruption ignored because a thread was not alive at the time of the interrupt * will be reflected by this method returning false. * * @return true if this thread has been interrupted; false otherwise. */ public static boolean isInteruppted() { return Thread.currentThread().isInterrupted(); } /** Prints the java stack trace of the current thread. */ public static void jstack() { jstack(1, -1); } /** * Prints the java stack trace of the current thread. But, atmost given number of frames. * * @param numFrames number of frames to be printed. When this is negative all frames are * printed. */ public static void jstack(int numFrames) { // passing '5' to skip our own frames to generate stack trace jstack(1, numFrames); } private static void jstack(int strip, int numFrames) { if (numFrames == 0) return; StackTraceElement[] st = Thread.currentThread().getStackTrace(); BTraceRuntime.stackTrace(st, strip + 2, numFrames); } /** Prints Java stack traces of all the Java threads. */ public static void jstackAll() { jstackAll(1, -1); } /** * Prints Java stack traces of all the Java threads. But, at most given number of frames. * * @param numFrames number of frames to be printed. When this is negative all frames are * printed. */ public static void jstackAll(int numFrames) { jstackAll(1, numFrames); } private static void jstackAll(int strip, int numFrames) { BTraceRuntime.stackTraceAll(numFrames); } /** * Returns the stack trace of current thread as a String. * * @return the stack trace as a String. */ public static String jstackStr() { return jstackStr(1, -1); } /** * Returns the stack trace of the current thread as a String but includes atmost the given * number of frames. * * @param numFrames number of frames to be included. When this is negative all frames are * included. * @return the stack trace as a String. */ public static String jstackStr(int numFrames) { if (numFrames == 0) { return ""; } return jstackStr(1, numFrames); } private static String jstackStr(int strip, int numFrames) { if (numFrames == 0) { return ""; } StackTraceElement[] st = Thread.currentThread().getStackTrace(); return BTraceRuntime.stackTraceStr(st, strip + 2, numFrames); } /** * Returns the stack traces of all Java threads as a String. * * @return the stack traces as a String. */ public static String jstackAllStr() { return jstackAllStr(-1); } /** * Returns atmost given number of frames in stack traces of all threads as a String. * * @param numFrames number of frames to be included. When this is negative all frames are * included. * @return the stack traces as a String. */ public static String jstackAllStr(int numFrames) { if (numFrames == 0) { return ""; } return BTraceRuntime.stackTraceAllStr(numFrames); } /** * Prints the stack trace of the given exception object. * * @param exception throwable for which stack trace is printed. */ public static void jstack(Throwable exception) { jstack(exception, -1); } /** * Prints the stack trace of the given exception object. But, prints atmost given number of * frames. * * @param exception throwable for which stack trace is printed. * @param numFrames maximum number of frames to be printed. */ public static void jstack(Throwable exception, int numFrames) { if (numFrames == 0) { return; } StackTraceElement[] st = exception.getStackTrace(); println(exception.toString()); BTraceRuntime.stackTrace("\t", st, 0, numFrames); Throwable cause = exception.getCause(); while (cause != null) { print("Caused by: "); println(cause.toString()); st = cause.getStackTrace(); BTraceRuntime.stackTrace("\t", st, 0, numFrames); cause = cause.getCause(); } } /** * Returns the stack trace of given exception object as a String. * * @param exception the throwable for which stack trace is returned. */ public static String jstackStr(Throwable exception) { return jstackStr(exception, -1); } /** * Returns stack trace of given exception object as a String. * * @param exception throwable for which stack trace is returned. * @param numFrames maximum number of frames to be returned. */ public static String jstackStr(Throwable exception, int numFrames) { if (numFrames == 0) { return ""; } StackTraceElement[] st = exception.getStackTrace(); StringBuilder buf = new StringBuilder(); buf.append(Strings.str(exception)); buf.append(BTraceRuntime.stackTraceStr("\t", st, 0, numFrames)); Throwable cause = exception.getCause(); while (cause != null) { buf.append("Caused by:"); st = cause.getStackTrace(); buf.append(BTraceRuntime.stackTraceStr("\t", st, 0, numFrames)); cause = cause.getCause(); } return buf.toString(); } /** * Returns a reference to the currently executing thread object. * * @return the currently executing thread. */ public static Thread currentThread() { return Thread.currentThread(); } /** * Returns the identifier of the given Thread. The thread ID is a positive long number * generated when the given thread was created. The thread ID is unique and remains unchanged * during its lifetime. When a thread is terminated, the thread ID may be reused. */ public static long threadId(Thread thread) { return thread.getId(); } /** * Returns the state of the given thread. This method is designed for use in monitoring of the * system state, not for synchronization control. */ public static Thread.State threadState(Thread thread) { return thread.getState(); } /** * Returns true if and only if the current thread holds the monitor lock on the * specified object. * *

This method is designed to allow a program to assert that the current thread already holds * a specified lock: * *

     *     assert Thread.holdsLock(obj);
     * 
* * @param obj the object on which to test lock ownership * @return true if the current thread holds the monitor lock on the specified object. * @throws NullPointerException if obj is null */ public static boolean holdsLock(Object obj) { return Thread.holdsLock(obj); } /** Prints the Java level deadlocks detected (if any). */ public static void deadlocks() { deadlocks(true); } /** * Prints deadlocks detected (if any). Optionally prints stack trace of the deadlocked threads. * * @param stackTrace boolean flag to specify whether to print stack traces of deadlocked threads * or not. */ public static void deadlocks(boolean stackTrace) { BTraceRuntime.deadlocks(stackTrace); } /** * Returns the name of the given thread. * * @param thread thread whose name is returned */ public static String name(Thread thread) { return thread.getName(); } } /* * Wraps the strings related BTrace utility methods * @since 1.2 */ public static class Strings { public static boolean startsWith(String s, String start) { return s.startsWith(start); } public static boolean endsWith(String s, String end) { return s.endsWith(end); } /** * This is synonym to "concat". * * @see #concat(String, String) */ public static String strcat(String str1, String str2) { return concat(str1, str2); } /** Concatenates the specified strings together. */ public static String concat(String str1, String str2) { return str1.concat(str2); } /** * Compares two strings lexicographically. The comparison is based on the Unicode value of each * character in the strings. The character sequence represented by the first String * object is compared lexicographically to the character sequence represented by the second * string. The result is a negative integer if the first String object * lexicographically precedes the second string. The result is a positive integer if the first * String object lexicographically follows the second string. The result is zero if * the strings are equal; compareTo returns 0 exactly when the {@link * String#equals(Object)} method would return true. */ public static int compareTo(String str1, String str2) { return str1.compareTo(str2); } /** * This is synonym to "compareTo" method. * * @see #compareTo */ public static int strcmp(String str1, String str2) { return str1.compareTo(str2); } /** * Compares two strings lexicographically, ignoring case differences. This method returns an * integer whose sign is that of calling compareTo with normalized versions of the * strings where case differences have been eliminated by calling * Character.toLowerCase(Character.toUpperCase(character)) on each character. */ public static int compareToIgnoreCase(String str1, String str2) { return str1.compareToIgnoreCase(str2); } /** * This is synonym to "compareToIgnoreCase". * * @see #compareToIgnoreCase */ public static int stricmp(String str1, String str2) { return str1.compareToIgnoreCase(str2); } /** Find String within String */ public static int strstr(String str1, String str2) { return str1.indexOf(str2); } public static int indexOf(String str1, String str2) { return str1.indexOf(str2); } public static int lastIndexOf(String str1, String str2) { return str1.lastIndexOf(str2); } /** Substring */ public static String substr(String str, int start, int length) { return str.substring(start, length); } public static String substr(String str, int start) { return str.substring(start); } /** * Returns the length of the given string. The length is equal to the number of Unicode code units in the string. * * @param str String whose length is calculated. * @return the length of the sequence of characters represented by this object. */ public static int length(String str) { return str.length(); } /** * This is synonym for "length". * * @see #length(String) */ public static int strlen(String str) { return str.length(); } // regular expression matching /** * Compiles the given regular expression into a pattern. * * @param regex The expression to be compiled * @throws PatternSyntaxException If the expression's syntax is invalid */ public static Pattern regexp(String regex) { return Pattern.compile(regex); } /** * This is synonym for "regexp". * * @see #regexp(String) */ public static Pattern pattern(String regex) { return regexp(regex); } /** * Compiles the given regular expression into a pattern with the given flags. * * @param regex The expression to be compiled * @param flags Match flags, a bit mask that may include {@link Pattern#CASE_INSENSITIVE}, * {@link Pattern#MULTILINE}, {@link Pattern#DOTALL}, {@link Pattern#UNICODE_CASE}, {@link * Pattern#CANON_EQ}, {@link Pattern#UNIX_LINES}, {@link Pattern#LITERAL} and {@link * Pattern#COMMENTS} * @throws IllegalArgumentException If bit values other than those corresponding to the defined * match flags are set in flags * @throws PatternSyntaxException If the expression's syntax is invalid */ public static Pattern regexp(String regex, int flags) { return Pattern.compile(regex, flags); } /** * This is synonym for "regexp". * * @see #regexp(String, int) */ public static Pattern pattern(String regex, int flags) { return regexp(regex, flags); } /** * Matches the given (precompiled) regular expression and attempts to match the given input * against it. */ public static boolean matches(Pattern regex, String input) { return regex.matcher(input).matches(); } /** * Compiles the given regular expression and attempts to match the given input against it. * *

An invocation of this convenience method of the form * *

* *
     * Pattern.matches(regex, input);
* *
* *

behaves in exactly the same way as the expression * *

* *
     * Pattern.compile(regex).matcher(input).matches()
* *
* *

If a pattern is to be used multiple times, compiling it once and reusing it will be more * efficient than invoking this method each time. * * @param regex The expression to be compiled * @param input The character sequence to be matched * @throws PatternSyntaxException If the expression's syntax is invalid */ public static boolean matches(String regex, String input) { return Pattern.matches(regex, input); } /** * Returns a String object representing the specified boolean. If the specified boolean * is true, then the string {@code "true"} will be returned, otherwise the string * {@code "false"} will be returned. * * @param b the boolean to be converted * @return the string representation of the specified boolean */ public static String str(boolean b) { return Boolean.toString(b); } /** * Returns a String object representing the specified char. The result * is a string of length 1 consisting solely of the specified char. * * @param c the char to be converted * @return the string representation of the specified char */ public static String str(char c) { return Character.toString(c); } /** * Returns a String object representing the specified integer. The argument is * converted to signed decimal representation and returned as a string. * * @param i an integer to be converted. * @return a string representation of the argument in base 10. */ public static String str(int i) { return Integer.toString(i); } /** * Returns a string representation of the integer argument as an unsigned integer in * base 16. * *

The unsigned integer value is the argument plus 232 if the argument is * negative; otherwise, it is equal to the argument. This value is converted to a string of * ASCII digits in hexadecimal (base 16) with no extra leading 0s. If the * unsigned magnitude is zero, it is represented by a single zero character '0' ( * '\u0030'); otherwise, the first character of the representation of the * unsigned magnitude will not be the zero character. The following characters are used as * hexadecimal digits: * *

* *
     * 0123456789abcdef
     * 
* *
* * These are the characters '\u0030' through '\u0039' and * '\u0061' through '\u0066'. * * @param i an integer to be converted to a string. * @return the string representation of the unsigned integer value represented by the argument * in hexadecimal (base 16). */ public static String toHexString(int i) { return Integer.toHexString(i); } /** * Returns a String object representing the specified long. The * argument is converted to signed decimal representation and returned as a string. * * @param l a long to be converted. * @return a string representation of the argument in base 10. */ public static String str(long l) { return Long.toString(l); } /** * Returns a string representation of the object. In general, the toString method * returns a string that "textually represents" this object. The result should be a concise but * informative representation that is easy for a person to read. For bootstrap classes, returns * the result of calling Object.toString() override. For non-bootstrap classes, default * toString() value [className@hashCode] is returned. * * @param obj the object whose string representation is returned * @return a string representation of the given object. */ public static String str(Object obj) { if (obj == null) { return "null"; } else if (obj instanceof String) { return (String) obj; } else if (obj.getClass().getClassLoader() == null) { try { return obj.toString(); } catch (NullPointerException e) { // NPE can be thrown from inside the toString() method we have no control over return "null"; } } else { return identityStr(obj); } } public static String str(Object[] array) { if (array == null) { return "null"; } StringBuilder buf = new StringBuilder(); buf.append('['); for (int i = 0; i < array.length; i++) { if (i > 0) { buf.append(", "); } buf.append(str(array[i])); } buf.append(']'); return buf.toString(); } /** * Returns a string representation of the long argument as an unsigned integer in * base 16. * *

The unsigned long value is the argument plus 264 if the argument * is negative; otherwise, it is equal to the argument. This value is converted to a string of * ASCII digits in hexadecimal (base 16) with no extra leading 0s. If the * unsigned magnitude is zero, it is represented by a single zero character '0' ( * '\u0030'); otherwise, the first character of the representation of the * unsigned magnitude will not be the zero character. The following characters are used as * hexadecimal digits: * *

* *
     * 0123456789abcdef
     * 
* *
* * These are the characters '\u0030' through '\u0039' and * '\u0061' through '\u0066'. * * @param l a long to be converted to a string. * @return the string representation of the unsigned long value represented by the * argument in hexadecimal (base 16). */ public static String toHexString(long l) { return Long.toHexString(l); } /** * Returns a string representation of the float argument. All characters mentioned * below are ASCII characters. * *
    *
  • If the argument is NaN, the result is the string "NaN". *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute * value) of the argument. If the sign is negative, the first character of the result is ' * -' ('\u002D'); if the sign is positive, no sign character * appears in the result. As for the magnitude m: *
      *
    • If m is infinity, it is represented by the characters "Infinity" * ; thus, positive infinity produces the result "Infinity" and * negative infinity produces the result "-Infinity". *
    • If m is zero, it is represented by the characters "0.0"; * thus, negative zero produces the result "-0.0" and positive zero * produces the result "0.0". *
    • If m is greater than or equal to 10-3 but less than * 107, then it is represented as the integer part of m, in * decimal form with no leading zeroes, followed by '.' ( * '\u002E'), followed by one or more decimal digits representing the * fractional part of m. *
    • If m is less than 10-3 or greater than or equal to * 107, then it is represented in so-called "computerized scientific * notation." Let n be the unique integer such that 10n * <= m < 10n+1; then let a be the * mathematically exact quotient of m and 10n so that 1 * <= a < 10. The magnitude is then represented as the integer part of * a, as a single decimal digit, followed by '.' ( * '\u002E'), followed by decimal digits representing the fractional part * of a, followed by the letter 'E' ('\u0045'), * followed by a representation of n as a decimal integer, as produced by the * method {@link * java.lang.Integer#toString(int)}. *
    *
* * How many digits must be printed for the fractional part of m or a? There must * be at least one digit to represent the fractional part, and beyond that as many, but only as * many, more digits as are needed to uniquely distinguish the argument value from adjacent * values of type float. That is, suppose that x is the exact mathematical * value represented by the decimal representation produced by this method for a finite nonzero * argument f. Then f must be the float value nearest to x; * or, if two float values are equally close to x, then f must be one * of them and the least significant bit of the significand of f must be 0. * *

* * @param f the float to be converted. * @return a string representation of the argument. */ public static String str(float f) { return Float.toString(f); } /** * Returns a string representation of the double argument. All characters mentioned * below are ASCII characters. * *

    *
  • If the argument is NaN, the result is the string "NaN". *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute * value) of the argument. If the sign is negative, the first character of the result is ' * -' ('\u002D'); if the sign is positive, no sign character * appears in the result. As for the magnitude m: *
      *
    • If m is infinity, it is represented by the characters "Infinity" * ; thus, positive infinity produces the result "Infinity" and * negative infinity produces the result "-Infinity". *
    • If m is zero, it is represented by the characters "0.0"; * thus, negative zero produces the result "-0.0" and positive zero * produces the result "0.0". *
    • If m is greater than or equal to 10-3 but less than * 107, then it is represented as the integer part of m, in * decimal form with no leading zeroes, followed by '.' ( * '\u002E'), followed by one or more decimal digits representing the * fractional part of m. *
    • If m is less than 10-3 or greater than or equal to * 107, then it is represented in so-called "computerized scientific * notation." Let n be the unique integer such that 10n * <= m < 10n+1; then let a be the * mathematically exact quotient of m and 10n so that 1 * <= a < 10. The magnitude is then represented as the integer part of * a, as a single decimal digit, followed by '.' ( * '\u002E'), followed by decimal digits representing the fractional part * of a, followed by the letter 'E' ('\u0045'), * followed by a representation of n as a decimal integer, as produced by the * method {@link Integer#toString(int)}. *
    *
* * How many digits must be printed for the fractional part of m or a? There must * be at least one digit to represent the fractional part, and beyond that as many, but only as * many, more digits as are needed to uniquely distinguish the argument value from adjacent * values of type double. That is, suppose that x is the exact mathematical * value represented by the decimal representation produced by this method for a finite nonzero * argument d. Then d must be the double value nearest to x; * or if two double values are equally close to x, then d must be one * of them and the least significant bit of the significand of d must be 0. * *

* * @param d the double to be converted. * @return a string representation of the argument. */ public static String str(double d) { return Double.toString(d); } /** * Safely creates a new instance of an appendable string buffer
* * @param threadSafe Specifies whether the buffer should be thread safe * @return Returns either {@linkplain StringBuilder} or {@linkplain StringBuffer} instance * depending on whether the instance is required to be thread safe or not, respectively. * @since 1.2 */ public static Appendable newStringBuilder(boolean threadSafe) { return BTraceRuntime.newStringBuilder(threadSafe); } /** * Safely creates a new instance of an appendable string buffer
* The buffer will not be thread safe. * * @return Returns a new instance of {@linkplain StringBuilder} class * @since 1.2 */ public static Appendable newStringBuilder() { return BTraceRuntime.newStringBuilder(); } /** * Appends a string to an appendable buffer created by {@linkplain * BTraceUtils.Strings#newStringBuilder()} * * @param buffer The appendable buffer to append to * @param strToAppend The string to append * @return Returns the same appendable buffer instance * @since 1.2 */ public static Appendable append(Appendable buffer, String strToAppend) { return BTraceRuntime.append(buffer, strToAppend); } /** * Checks the length of an appendable buffer created by {@linkplain * BTraceUtils.Strings#newStringBuilder()} * * @param buffer The appendable buffer instance * @return Returns the length of the text contained by the buffer * @since 1.2 */ public static int length(Appendable buffer) { return BTraceRuntime.length(buffer); } } /* * Wraps the numbers related BTrace utility methods * @since 1.2 */ public static class Numbers { /** * Returns a double value with a positive sign, greater than or equal to 0.0 * and less than 1.0. Returned values are chosen pseudorandomly with * (approximately) uniform distribution from that range. */ public static double random() { return Math.random(); } /** * Returns the natural logarithm (base e) of a double value. Special cases: * *

    *
  • If the argument is NaN or less than zero, then the result is NaN. *
  • If the argument is positive infinity, then the result is positive infinity. *
  • If the argument is positive zero or negative zero, then the result is negative * infinity. *
* *

The computed result must be within 1 ulp of the exact result. Results must be * semi-monotonic. * * @param a a value * @return the value ln a, the natural logarithm of a. */ public static strictfp double log(double a) { return Math.log(a); } /** * Returns the base 10 logarithm of a double value. Special cases: * *

    *
  • If the argument is NaN or less than zero, then the result is NaN. *
  • If the argument is positive infinity, then the result is positive infinity. *
  • If the argument is positive zero or negative zero, then the result is negative * infinity. *
  • If the argument is equal to 10n for integer n, then the result * is n. *
* *

The computed result must be within 1 ulp of the exact result. Results must be * semi-monotonic. * * @param a a value * @return the base 10 logarithm of a. */ public static strictfp double log10(double a) { return Math.log10(a); } /** * Returns Euler's number e raised to the power of a double value. Special * cases: * *

    *
  • If the argument is NaN, the result is NaN. *
  • If the argument is positive infinity, then the result is positive infinity. *
  • If the argument is negative infinity, then the result is positive zero. *
* *

The computed result must be within 1 ulp of the exact result. Results must be * semi-monotonic. * * @param a the exponent to raise e to. * @return the value ea, where e is the base of the * natural logarithms. */ public static strictfp double exp(double a) { return Math.exp(a); } /** * Returns true if the specified number is a Not-a-Number (NaN) value, false * otherwise. * * @param d the value to be tested. * @return true if the value of the argument is NaN; false otherwise. */ public static boolean isNaN(double d) { return Double.isNaN(d); } /** * Returns true if the specified number is a Not-a-Number (NaN) value, false * otherwise. * * @param f the value to be tested. * @return true if the value of the argument is NaN; false otherwise. */ public static boolean isNaN(float f) { return Float.isNaN(f); } /** * Returns true if the specified number is infinitely large in magnitude, * false otherwise. * * @param d the value to be tested. * @return true if the value of the argument is positive infinity or negative * infinity; false otherwise. */ public static boolean isInfinite(double d) { return Double.isInfinite(d); } /** * Returns true if the specified number is infinitely large in magnitude, * false otherwise. * * @param f the value to be tested. * @return true if the value of the argument is positive infinity or negative * infinity; false otherwise. */ public static boolean isInfinite(float f) { return Float.isInfinite(f); } // string parsing methods /** * Parses the string argument as a boolean. The boolean returned represents the * value true if the string argument is not null and is equal, * ignoring case, to the string {@code "true"}. * *

Example: {@code Boolean.parseBoolean("True")} returns true.
* Example: {@code Boolean.parseBoolean("yes")} returns false. * * @param s the String containing the boolean representation to be parsed * @return the boolean represented by the string argument */ public static boolean parseBoolean(String s) { return Boolean.parseBoolean(s); } /** * Parses the string argument as a signed decimal byte. The characters in the * string must all be decimal digits, except that the first character may be an ASCII minus sign * '-' ('\u002D') to indicate a negative value. The resulting * byte value is returned. * * @param s a String containing the byte representation to be parsed * @return the byte value represented by the argument in decimal */ public static byte parseByte(String s) { return Byte.parseByte(s); } /** * Parses the string argument as a signed decimal short. The characters in the * string must all be decimal digits, except that the first character may be an ASCII minus sign * '-' ('\u002D') to indicate a negative value. The resulting * short value is returned. * * @param s a String containing the short representation to be parsed * @return the short value represented by the argument in decimal. */ public static short parseShort(String s) { return Short.parseShort(s); } /** * Parses the string argument as a signed decimal integer. The characters in the string must all * be decimal digits, except that the first character may be an ASCII minus sign '-' * ('\u002D') to indicate a negative value. The resulting integer value * is returned. * * @param s a String containing the int representation to be parsed * @return the integer value represented by the argument in decimal. */ public static int parseInt(String s) { return Integer.parseInt(s); } /** * Parses the string argument as a signed decimal long. The characters in the * string must all be decimal digits, except that the first character may be an ASCII minus sign * '-' (\u002D') to indicate a negative value. The resulting * long value is returned. * *

Note that neither the character L ('\u004C') nor l * ('\u006C') is permitted to appear at the end of the string as a type * indicator, as would be permitted in Java programming language source code. * * @param s a String containing the long representation to be parsed * @return the long represented by the argument in decimal. */ public static long parseLong(String s) { return Long.parseLong(s); } /** * Returns a new float initialized to the value represented by the specified * String, as performed by the valueOf method of class Float. * * @param s the string to be parsed. * @return the float value represented by the string argument. */ public static float parseFloat(String s) { return Float.parseFloat(s); } /** * Returns a new double initialized to the value represented by the specified * String, as performed by the valueOf methcod of class Double * . * * @param s the string to be parsed. * @return the double value represented by the string argument. */ public static double parseDouble(String s) { return Double.parseDouble(s); } // boxing methods /** * Returns a Boolean instance representing the specified boolean value. If the * specified boolean value is true, this method returns Boolean.TRUE; * if it is false, this method returns Boolean.FALSE. * * @param b a boolean value. * @return a Boolean instance representing b. */ public static Boolean box(boolean b) { return b; } /** * Returns a Character instance representing the specified char value. * * @param c a char value. * @return a Character instance representing c. */ public static Character box(char c) { return c; } /** * Returns a Byte instance representing the specified byte value. * * @param b a byte value. * @return a Byte instance representing b. */ public static Byte box(byte b) { return b; } /** * Returns a Short instance representing the specified short value. * * @param s a short value. * @return a Short instance representing s. */ public static Short box(short s) { return s; } /** * Returns a Integer instance representing the specified int value. * * @param i an int value. * @return a Integer instance representing i. */ public static Integer box(int i) { return i; } /** * Returns a Long instance representing the specified long value. * * @param l a long value. * @return a Long instance representing l. */ public static Long box(long l) { return l; } /** * Returns a Float instance representing the specified float value. * * @param f a float value. * @return a Float instance representing f. */ public static Float box(float f) { return f; } /** * Returns a Double instance representing the specified double value. * * @param d a double value. * @return a Double instance representing d. */ public static Double box(double d) { return d; } // unboxing methods /** * Returns the value of the given Boolean object as a boolean primitive. * * @param b the Boolean object whose value is returned. * @return the primitive boolean value of the object. */ public static boolean unbox(Boolean b) { return b; } /** * Returns the value of the given Character object as a char primitive. * * @param ch the Character object whose value is returned. * @return the primitive char value of the object. */ public static char unbox(Character ch) { return ch; } /** * Returns the value of the specified Byte as a byte. * * @param b Byte that is unboxed * @return the byte value represented by the Byte. */ public static byte unbox(Byte b) { return b; } /** * Returns the short value represented by Short. * * @param s Short that is unboxed. * @return the short value represented by the Short. */ public static short unbox(Short s) { return s; } /** * Returns the value of represented by Integer. * * @param i Integer that is unboxed. * @return the int value represented by the Integer. */ public static int unbox(Integer i) { return i; } /** * Returns the long value represented by the specified Long. * * @param l Long to be unboxed. * @return the long value represented by the Long. */ public static long unbox(Long l) { return l; } /** * Returns the float value represented by the specified Float. * * @param f Float to be unboxed. * @return the float value represented by the Float. */ public static float unbox(Float f) { return f; } /** * Returns the double value represented by the specified Double. * * @param d Double to be unboxed. */ public static double unbox(Double d) { return d; } } /* * Wraps the time related BTrace utility methods * @since 1.2 */ public static class Time { /** * Returns the current time in milliseconds. Note that while the unit of time of the return * value is a millisecond, the granularity of the value depends on the underlying operating * system and may be larger. For example, many operating systems measure time in units of tens * of milliseconds. * * @return the difference, measured in milliseconds, between the current time and midnight, * January 1, 1970 UTC. */ public static long millis() { return java.lang.System.currentTimeMillis(); } /** * Returns the current value of the most precise available system timer, in nanoseconds. * *

This method can only be used to measure elapsed time and is not related to any other * notion of system or wall-clock time. The value returned represents nanoseconds since some * fixed but arbitrary time (perhaps in the future, so values may be negative). This method * provides nanosecond precision, but not necessarily nanosecond accuracy. No guarantees are * made about how frequently values change. Differences in successive calls that span greater * than approximately 292 years (263 nanoseconds) will not accurately compute elapsed * time due to numerical overflow. * * @return The current value of the system timer, in nanoseconds. */ public static long nanos() { return java.lang.System.nanoTime(); } /** * Generates a string timestamp (current date&time) * * @param format The format to be used - see {@linkplain SimpleDateFormat} * @return Returns a string representing current date&time * @since 1.1 */ public static String timestamp(String format) { return new SimpleDateFormat(format).format(Calendar.getInstance().getTime()); } /** * Generates a string timestamp (current date&time) in the default system format * * @return Returns a string representing current date&time * @since 1.1 */ public static String timestamp() { return new SimpleDateFormat().format(Calendar.getInstance().getTime()); } } /* * Wraps the collections related BTrace utility methods * @since 1.2 */ @SuppressWarnings("EmptyMethod") public static class Collections { // Create a new map public static Map newHashMap() { return BTraceRuntime.newHashMap(); } public static Map newWeakMap() { return BTraceRuntime.newWeakMap(); } public static Deque newDeque() { return BTraceRuntime.newDeque(); } public static void putAll(Map src, Map dst) { BTraceRuntime.putAll(src, dst); } public static void copy(Map src, Map dst) { BTraceRuntime.copy(src, dst); } public static void copy(Collection src, Collection dst) {} // get a particular item from a Map public static V get(Map map, K key) { return BTraceRuntime.get(map, key); } // check whether an item exists public static boolean containsKey(Map map, K key) { return BTraceRuntime.containsKey(map, key); } public static boolean containsValue(Map map, V value) { return BTraceRuntime.containsValue(map, value); } // put a particular item into a Map public static V put(Map map, K key, V value) { return BTraceRuntime.put(map, key, value); } // remove a particular item from a Map public static V remove(Map map, K key) { return BTraceRuntime.remove(map, key); } // clear all items from a Map public static void clear(Map map) { BTraceRuntime.clear(map); } // return the size of a Map public static int size(Map map) { return BTraceRuntime.size(map); } public static boolean isEmpty(Map map) { return BTraceRuntime.isEmpty(map); } // operations on collections public static int size(Collection coll) { return BTraceRuntime.size(coll); } public static boolean isEmpty(Collection coll) { return BTraceRuntime.isEmpty(coll); } public static boolean contains(Collection coll, Object obj) { return BTraceRuntime.contains(coll, obj); } public static boolean contains(Object[] array, Object value) { for (Object each : array) { if (compare(each, value)) { return true; } } return false; } public static Object[] toArray(Collection collection) { return BTraceRuntime.toArray(collection); } // operations on Deque public static void push(Deque queue, V value) { BTraceRuntime.push(queue, value); } public static V poll(Deque queue) { return BTraceRuntime.poll(queue); } public static V peek(Deque queue) { return BTraceRuntime.peek(queue); } public static void addLast(Deque queue, V value) { BTraceRuntime.addLast(queue, value); } public static V peekFirst(Deque queue) { return BTraceRuntime.peekFirst(queue); } public static V peekLast(Deque queue) { return BTraceRuntime.peekLast(queue); } public static V removeLast(Deque queue) { return BTraceRuntime.removeLast(queue); } public static V removeFirst(Deque queue) { return BTraceRuntime.removeFirst(queue); } } /* * Wraps the atomicity related BTrace utility methods * @since 1.2 */ public static class Atomic { /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public static AtomicInteger newAtomicInteger(int initialValue) { return BTraceRuntime.newAtomicInteger(initialValue); } /** * Gets the current value of the given AtomicInteger. * * @param ai AtomicInteger whose value is returned. * @return the current value */ public static int get(AtomicInteger ai) { return BTraceRuntime.get(ai); } /** * Sets to the given value to the given AtomicInteger. * * @param ai AtomicInteger whose value is set. * @param newValue the new value */ public static void set(AtomicInteger ai, int newValue) { BTraceRuntime.set(ai, newValue); } /** * Eventually sets to the given value to the given AtomicInteger. * * @param ai AtomicInteger whose value is lazily set. * @param newValue the new value */ public static void lazySet(AtomicInteger ai, int newValue) { BTraceRuntime.lazySet(ai, newValue); } /** * Atomically sets the value of given AtomitInteger to the given updated value if the current * value {@code ==} the expected value. * * @param ai AtomicInteger whose value is compared and set. * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that the actual value was not equal to the * expected value. */ public static boolean compareAndSet(AtomicInteger ai, int expect, int update) { return BTraceRuntime.compareAndSet(ai, expect, update); } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. * *

May fail spuriously and does not provide * ordering guarantees, so is only rarely an appropriate alternative to {@code compareAndSet}. * * @param ai AtomicInteger whose value is weakly compared and set. * @param expect the expected value * @param update the new value * @return true if successful. */ public static boolean weakCompareAndSet(AtomicInteger ai, int expect, int update) { return BTraceRuntime.weakCompareAndSet(ai, expect, update); } /** * Atomically increments by one the current value of given AtomicInteger. * * @param ai AtomicInteger that is incremented. * @return the previous value */ public static int getAndIncrement(AtomicInteger ai) { return BTraceRuntime.getAndIncrement(ai); } /** * Atomically decrements by one the current value of given AtomicInteger. * * @param ai AtomicInteger that is decremented. * @return the previous value */ public static int getAndDecrement(AtomicInteger ai) { return BTraceRuntime.getAndDecrement(ai); } /** * Atomically increments by one the current value of given AtomicInteger. * * @param ai AtomicInteger that is incremented. * @return the updated value */ public static int incrementAndGet(AtomicInteger ai) { return BTraceRuntime.incrementAndGet(ai); } /** * Atomically decrements by one the current value of given AtomicInteger. * * @param ai AtomicInteger whose value is decremented. * @return the updated value */ public static int decrementAndGet(AtomicInteger ai) { return BTraceRuntime.decrementAndGet(ai); } /** * Atomically adds the given value to the current value. * * @param ai AtomicInteger whose value is added to. * @param delta the value to add * @return the previous value */ public static int getAndAdd(AtomicInteger ai, int delta) { return BTraceRuntime.getAndAdd(ai, delta); } /** * Atomically adds the given value to the current value. * * @param ai AtomicInteger whose value is added to. * @param delta the value to add * @return the updated value */ public static int addAndGet(AtomicInteger ai, int delta) { return BTraceRuntime.addAndGet(ai, delta); } /** * Atomically sets to the given value and returns the old value. * * @param ai AtomicInteger whose value is set. * @param newValue the new value * @return the previous value */ public static int getAndSet(AtomicInteger ai, int newValue) { return BTraceRuntime.getAndSet(ai, newValue); } /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public static AtomicLong newAtomicLong(long initialValue) { return BTraceRuntime.newAtomicLong(initialValue); } /** * Gets the current value the given AtomicLong. * * @param al AtomicLong whose value is returned. * @return the current value */ public static long get(AtomicLong al) { return BTraceRuntime.get(al); } /** * Sets to the given value. * * @param al AtomicLong whose value is set. * @param newValue the new value */ public static void set(AtomicLong al, long newValue) { BTraceRuntime.set(al, newValue); } /** * Eventually sets to the given value to the given AtomicLong. * * @param al AtomicLong whose value is set. * @param newValue the new value */ public static void lazySet(AtomicLong al, long newValue) { BTraceRuntime.lazySet(al, newValue); } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. * * @param al AtomicLong whose value is compared and set. * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that the actual value was not equal to the * expected value. */ public static boolean compareAndSet(AtomicLong al, long expect, long update) { return BTraceRuntime.compareAndSet(al, expect, update); } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. * *

May fail spuriously and does not provide ordering guarantees, so is only rarely an * appropriate alternative to {@code compareAndSet}. * * @param al AtomicLong whose value is compared and set. * @param expect the expected value * @param update the new value * @return true if successful. */ public static boolean weakCompareAndSet(AtomicLong al, long expect, long update) { return BTraceRuntime.weakCompareAndSet(al, expect, update); } /** * Atomically increments by one the current value. * * @param al AtomicLong whose value is incremented. * @return the previous value */ public static long getAndIncrement(AtomicLong al) { return BTraceRuntime.getAndIncrement(al); } /** * Atomically decrements by one the current value. * * @param al AtomicLong whose value is decremented. * @return the previous value */ public static long getAndDecrement(AtomicLong al) { return BTraceRuntime.getAndDecrement(al); } /** * Atomically increments by one the current value. * * @param al AtomicLong whose value is incremented. * @return the updated value */ public static long incrementAndGet(AtomicLong al) { return BTraceRuntime.incrementAndGet(al); } /** * Atomically decrements by one the current value. * * @param al AtomicLong whose value is decremented. * @return the updated value */ public static long decrementAndGet(AtomicLong al) { return BTraceRuntime.decrementAndGet(al); } /** * Atomically adds the given value to the current value. * * @param al AtomicLong whose value is added to. * @param delta the value to add * @return the previous value */ public static long getAndAdd(AtomicLong al, long delta) { return BTraceRuntime.getAndAdd(al, delta); } /** * Atomically adds the given value to the current value. * * @param al AtomicLong whose value is added to * @param delta the value to add * @return the updated value */ public static long addAndGet(AtomicLong al, long delta) { return BTraceRuntime.addAndGet(al, delta); } /** * Atomically sets to the given value and returns the old value. * * @param al AtomicLong that is set. * @param newValue the new value * @return the previous value */ public static long getAndSet(AtomicLong al, long newValue) { return BTraceRuntime.getAndSet(al, newValue); } } /** * Profiling support. It is a highly specialized aggregation (therefore not included in the * generic aggregations support) which is able to calculate clean self time spent in * hierarchically called methods (or bigger parts of code) */ public static class Profiling { /** * Creates a new {@linkplain Profiler} instance * * @return A new {@linkplain Profiler} instance */ public static Profiler newProfiler() { return BTraceRuntime.newProfiler(); } /** * Creates a new {@linkplain Profiler} instance with the specified expected count of the * distinct methods to be recorded. * * @param expectedBlockCnt The expected count of the distinct blocks to be recorded. * @return Returns a new {@linkplain Profiler} instance */ public static Profiler newProfiler(int expectedBlockCnt) { return BTraceRuntime.newProfiler(expectedBlockCnt); } /** * Records the entry to a particular code block * * @param profiler The {@linkplain Profiler} instance to use * @param blockName The block identifier */ public static void recordEntry(Profiler profiler, String blockName) { BTraceRuntime.recordEntry(profiler, blockName); } /** * Records the exit out of a particular code block * * @param profiler The {@linkplain Profiler} instance to use * @param blockName The block identifier * @param duration The time spent in the mentioned block */ public static void recordExit(Profiler profiler, String blockName, long duration) { BTraceRuntime.recordExit(profiler, blockName, duration); } /** * Creates a new snapshot of the profiling metrics collected sofar * * @param profiler The {@linkplain Profiler} instance to use * @return Returns an immutable snapshot of the profiling metrics in the form of a map where the * key is the block name and the value is a map of metrics names and the appropriate values *
* The supported metrics names are: "selfTime", "wallTime" and "invocations" */ public static Profiler.Snapshot snapshot(Profiler profiler) { return BTraceRuntime.snapshot(profiler); } public static Profiler.Snapshot snapshotAndReset(Profiler profiler) { return BTraceRuntime.snapshotAndReset(profiler); } public static void reset(Profiler profiler) { BTraceRuntime.resetProfiler(profiler); } public static void printSnapshot(String name, Profiler profiler) { BTraceRuntime.printSnapshot(name, profiler.snapshot()); } public static void printSnapshot(String name, Profiler profiler, String format) { BTraceRuntime.printSnapshot(name, profiler.snapshot(), format); } } /* * Wraps the speculation related BTrace utility methods * @since 1.2 */ public static class Speculation { /** * Returns an identifier for a new speculative buffer. * * @return new speculative buffer id */ public static int speculation() { return BTraceRuntime.speculation(); } /** * Sets current speculative buffer id. * * @param id the speculative buffer id */ public static void speculate(int id) { BTraceRuntime.speculate(id); } /** * Commits the speculative buffer associated with id. * * @param id the speculative buffer id */ public static void commit(int id) { BTraceRuntime.commit(id); } /** * Discards the speculative buffer associated with id. * * @param id the speculative buffer id */ public static void discard(int id) { BTraceRuntime.discard(id); } } /* * Wraps the references related BTrace utility methods * @since 1.2 */ public static class References { /** * Creates and returns a weak reference to the given object. * * @param obj object for which a weak reference is created. * @return a weak reference to the given object. */ public static WeakReference weakRef(Object obj) { return new WeakReference<>(obj); } /** * Creates and returns a soft reference to the given object. * * @param obj object for which a soft reference is created. * @return a soft reference to the given object. */ public static SoftReference softRef(Object obj) { return new SoftReference<>(obj); } /** * Returns the given reference object's referent. If the reference object has been cleared, * either by the program or by the garbage collector, then this method returns null * . * * @param ref reference object whose referent is returned. * @return The object to which the reference refers, or null if the reference * object has been cleared. */ public static Object deref(Reference ref) { if (ref.getClass().getClassLoader() == null) { return ref.get(); } else { throw new IllegalArgumentException(); } } } /* * Wraps the reflection related BTrace utility methods * @since 1.2 */ public static class Reflective { /** * Returns the runtime class of the given Object. * * @param obj the Object whose Class is returned * @return the Class object of given object */ public static Class classOf(Object obj) { return obj.getClass(); } /** * Returns the Class object representing the class or interface that declares the field * represented by the given Field object. * * @param field whose declaring Class is returned */ public static Class declaringClass(Field field) { return field.getDeclaringClass(); } /** Returns the name of the given Class object. */ public static String name(Class clazz) { return clazz.getName(); } /** * Returns the name of the Field object. * * @param field Field for which name is returned * @return name of the given field */ public static String name(Field field) { return field.getName(); } /** * Returns the type of the Field object. * * @param field Field for which type is returned * @return type of the given field */ public static Class type(Field field) { return field.getType(); } /** Returns the access flags of the given Class. */ public static int accessFlags(Class clazz) { return clazz.getModifiers(); } /** Returns the access flags of the given Field. */ public static int accessFlags(Field field) { return field.getModifiers(); } /** Returns the current context class loader */ public static ClassLoader contextClassLoader() { return Thread.currentThread().getContextClassLoader(); } // get Class of the given name /** Returns Class object for given class name. */ public static Class classForName(String name) { ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); return classForName(name, callerLoader); } /** Returns the Class for the given class name using the given class loader. */ public static Class classForName(String name, ClassLoader cl) { try { return Class.forName(name, false, cl); } catch (ClassNotFoundException exp) { throw translate(exp); } } /** * Determines if the class or interface represented by the first Class object is * either the same as, or is a superclass or superinterface of, the class or interface * represented by the second Class parameter. It returns true if so; * otherwise it returns false. */ public static boolean isAssignableFrom(Class a, Class b) { return a.isAssignableFrom(b); } /** * Determines if the specified Object is assignment-compatible with the object * represented by the specified Class. This method is the dynamic equivalent of the * Java language instanceof operator. The method returns true if the * specified Object argument is non-null and can be cast to the reference type * represented by this Class object without raising a ClassCastException. * It returns false otherwise. * * @param clazz the class that is checked. * @param obj the object to check. * @return true if obj is an instance of the given class. */ public static boolean isInstance(Class clazz, Object obj) { return clazz.isInstance(obj); } /** * Returns the Class representing the superclass of the entity (class, interface, * primitive type or void) represented by the given Class. If the given Class * represents either the Object class, an interface, a primitive type, or * void, then null is returned. If the given object represents an array class then the * Class object representing the Object class is returned. * * @param clazz the Class whose super class is returned. * @return the superclass of the class represented by the given object. */ public static Class getSuperclass(Class clazz) { return clazz.getSuperclass(); } /** * Determines if the specified Class object represents an interface type. * * @param clazz the Class object to check. * @return true if the Class represents an interface; false otherwise. */ public static boolean isInterface(Class clazz) { return clazz.isInterface(); } /** * Determines if the given Class object represents an array class. * * @param clazz Class object to check. * @return true if the given object represents an array class; false * otherwise. */ public static boolean isArray(Class clazz) { return clazz.isArray(); } /** Returns whether the given Class represent primitive type or not. */ public static boolean isPrimitive(Class clazz) { return clazz.isPrimitive(); } /** returns component type of an array Class. */ public static Class getComponentType(Class clazz) { return clazz.getComponentType(); } // Accessing fields by reflection /** * Returns a Field object that reflects the specified declared field of the class * or interface represented by the given Class object. The name * parameter is a String that specifies the simple name of the desired field. * Returns null on not finding field if throwException parameter is false * . Else throws a RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @param throwException whether to throw exception on failing to find field or not * @return the Field object for the specified field in this class */ public static Field field(Class clazz, String name, boolean throwException) { return getField(clazz, name, throwException); } /** * Returns a Field object that reflects the specified declared field of the class * or interface represented by the given Class object. The name * parameter is a String that specifies the simple name of the desired field. * Throws a RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @return the Field object for the specified field in this class */ public static Field field(Class clazz, String name) { return field(clazz, name, true); } /** * Returns a Field object that reflects the specified declared field of the class * or interface represented by the given Class object. The name * parameter is a String that specifies the simple name of the desired field. * Returns null on not finding field if throwException parameter is false * . Else throws a RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @param throwException whether to throw exception on failing to find field or not * @return the Field object for the specified field in this class */ public static Field field(String clazz, String name, boolean throwException) { ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); return field(classForName(clazz, callerLoader), name, throwException); } /** * Returns a Field object that reflects the specified declared field of the class * or interface represented by the given Class object. The name * parameter is a String that specifies the simple name of the desired field. * Throws a RuntimeException when field is not found. * * @param clazz Class whose field is returned * @param name the name of the field * @return the Field object for the specified field in this class */ public static Field field(String clazz, String name) { ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); return field(classForName(clazz, callerLoader), name); } // field value get methods /** * Gets the value of a static byte field. * * @param field Field object whose value is returned. * @return the value of the byte field */ public static byte getByte(Field field) { checkStatic(field); try { return field.getByte(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance byte field. * * @param field Field object whose value is returned. * @param obj the object to extract the byte value from * @return the value of the byte field */ public static byte getByte(Field field, Object obj) { try { return field.getByte(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance byte field. * * @param name name of the field whose value is returned. * @param instance the object to extract the byte value from * @return the value of the byte field * @since 1.3.5 */ public static byte getByte(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getByte(f, instance); } /** * Gets the value of a static byte field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the byte value from * @return the value of the byte field * @since 1.3.5 */ public static byte getByteStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getByte(f, null); } /** * Gets the value of a static short field. * * @param field Field object whose value is returned. * @return the value of the short field */ public static short getShort(Field field) { checkStatic(field); try { return field.getShort(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance short field. * * @param field Field object whose value is returned. * @param obj the object to extract the short value from * @return the value of the short field */ public static short getShort(Field field, Object obj) { try { return field.getShort(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance short field. * * @param name name of the field whose value is returned. * @param instance the object to extract the short value from * @return the value of the short field * @since 1.3.5 */ public static short getShort(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getShort(f, instance); } /** * Gets the value of a static short field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the short value from * @return the value of the short field * @since 1.3.5 */ public static short getShortStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getShort(f, null); } /** * Gets the value of a static int field. * * @param field Field object whose value is returned. * @return the value of the int field */ public static int getInt(Field field) { checkStatic(field); try { return field.getInt(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance int field. * * @param field Field object whose value is returned. * @param obj the object to extract the int value from * @return the value of the int field */ public static int getInt(Field field, Object obj) { try { return field.getInt(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance int field. * * @param name name of the field whose value is returned. * @param instance the object to extract the int value from * @return the value of the int field * @since 1.3.5 */ public static int getInt(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getInt(f, instance); } /** * Gets the value of a sttaic int field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the int value from * @return the value of the int field * @since 1.3.5 */ public static int getIntStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getInt(f, null); } /** * Gets the value of a static long field. * * @param field Field object whose value is returned. * @return the value of the long field */ public static long getLong(Field field) { checkStatic(field); try { return field.getLong(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance long field. * * @param field Field object whose value is returned. * @param obj the object to extract the long value from * @return the value of the long field */ public static long getLong(Field field, Object obj) { try { return field.getLong(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance long field. * * @param name name of the field whose value is returned. * @param instance the object to extract the long value from * @return the value of the long field * @since 1.3.5 */ public static long getLong(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getLong(f, instance); } /** * Gets the value of a static long field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the long value from * @return the value of the long field * @since 1.3.5 */ public static long getLongStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getLong(f, null); } /** * Gets the value of a static float field. * * @param field Field object whose value is returned. * @return the value of the float field */ public static float getFloat(Field field) { checkStatic(field); try { return field.getFloat(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance float field. * * @param field Field object whose value is returned. * @param obj the object to extract the float value from * @return the value of the float field */ public static float getFloat(Field field, Object obj) { try { return field.getFloat(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance float field. * * @param name name of the field whose value is returned. * @param instance the object to extract the float value from * @return the value of the float field * @since 1.3.5 */ public static float getFloat(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getFloat(f, instance); } /** * Gets the value of a static float field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the float value from * @return the value of the float field * @since 1.3.5 */ public static float getFloatStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getFloat(f, null); } /** * Gets the value of a static double field. * * @param field Field object whose value is returned. * @return the value of the double field */ public static double getDouble(Field field) { checkStatic(field); try { return field.getDouble(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance double field. * * @param field Field object whose value is returned. * @param instance the object to extract the double value from * @return the value of the double field */ public static double getDouble(Field field, Object instance) { try { return field.getDouble(instance); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance double field. * * @param name name of the field whose value is returned. * @param instance the object to extract the double value from * @return the value of the double field * @since 1.3.5 */ public static double getDouble(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getDouble(f, instance); } /** * Gets the value of a static double field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the double value from * @return the value of the double field * @since 1.3.5 */ public static double getDouble(String name, Class clazz) { Field f = getField(clazz, name, true); return getDouble(f, null); } /** * Gets the value of a static boolean field. * * @param field Field object whose value is returned. * @return the value of the boolean field */ public static boolean getBoolean(Field field) { checkStatic(field); try { return field.getBoolean(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance boolean field. * * @param field Field object whose value is returned. * @param obj the object to extract the boolean value from * @return the value of the boolean field */ public static boolean getBoolean(Field field, Object obj) { try { return field.getBoolean(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance boolean field. * * @param name name of the field whose value is returned. * @param instance the object to extract the boolean value from * @return the value of the boolean field * @since 1.3.5 */ public static boolean getBoolean(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getBoolean(f, instance); } /** * Gets the value of a static boolean field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the boolean value from * @return the value of the boolean field * @since 1.3.5 */ public static boolean getBooleanStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getBoolean(f, null); } /** * Gets the value of a static char field. * * @param field Field object whose value is returned. * @return the value of the char field */ public static char getChar(Field field) { checkStatic(field); try { return field.getChar(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance char field. * * @param field Field object whose value is returned. * @param obj the object to extract the char value from * @return the value of the char field */ public static char getChar(Field field, Object obj) { try { return field.getChar(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance char field. * * @param name name of the field whose value is returned. * @param instance the object to extract the char value from * @return the value of the char field * @since 1.3.5 */ public static char getChar(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return getChar(f, instance); } /** * Gets the value of a static char field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the char value from * @return the value of the char field * @since 1.3.5 */ public static char getCharStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return getChar(f, null); } /** * Gets the value of a static reference field. * * @param field Field object whose value is returned. * @return the value of the reference field */ public static Object get(Field field) { checkStatic(field); try { return field.get(null); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance reference field. * * @param field Field object whose value is returned. * @param obj the object to extract the reference value from * @return the value of the reference field */ public static Object get(Field field, Object obj) { try { return field.get(obj); } catch (Exception exp) { throw translate(exp); } } /** * Gets the value of an instance reference field. * * @param name name of the field whose value is returned. * @param instance the object to extract the reference value from * @return the value of the reference field * @since 1.3.5 */ public static Object get(String name, Object instance) { Field f = getField(instance.getClass(), name, true); return get(f, instance); } /** * Gets the value of a static reference field. * * @param name name of the field whose value is returned. * @param clazz the class to extract the reference value from * @return the value of the reference field * @since 1.3.5 */ public static Object getStatic(String name, Class clazz) { Field f = getField(clazz, name, true); return get(f, null); } /** * Print all instance fields of an object as name-value pairs. Includes the inherited fields as * well. * * @param obj Object whose fields are printed. */ public static void printFields(Object obj) { printFields(obj, false); } /** * Print all instance fields of an object as name-value pairs. Includes the inherited fields as * well. Optionally, prints name of the declaring class before each field - so that if same * named field in super class chain may be disambiguated. * * @param obj Object whose fields are printed. * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. */ public static void printFields(Object obj, boolean classNamePrefix) { StringBuilder buf = new StringBuilder(); buf.append('{'); addFieldValues(buf, obj, obj.getClass(), classNamePrefix); buf.append('}'); println(buf.toString()); } /** * Print all static fields of the class as name-value pairs. Includes the inherited fields as * well. * * @param clazz Class whose static fields are printed. */ public static void printStaticFields(Class clazz) { printStaticFields(clazz, false); } /** * Print all static fields of the class as name-value pairs. Includes the inherited fields as * well. Optionally, prints name of the declaring class before each field - so that if same * named field in super class chain may be disambigated. * * @param clazz Class whose static fields are printed. * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. */ public static void printStaticFields(Class clazz, boolean classNamePrefix) { StringBuilder buf = new StringBuilder(); buf.append('{'); addStaticFieldValues(buf, clazz, classNamePrefix); buf.append('}'); println(buf.toString()); } } /* * Wraps the data export related BTrace utility methods * @since 1.2 */ public static class Export { /** * Serialize a given object into the given file. Under the current dir of traced app, * ./btrace<pid>/btrace-class/ directory is created. Under that directory, a file of given * fileName is created. * * @param obj object that has to be serialized. * @param fileName name of the file to which the object is serialized. */ public static void serialize(Serializable obj, String fileName) { BTraceRuntime.serialize(obj, fileName); } /** * Creates an XML document to persist the tree of the all transitively reachable objects from * given "root" object. */ public static String toXML(Object obj) { return BTraceRuntime.toXML(obj); } /** * Writes an XML document to persist the tree of the all the transitively reachable objects from * the given "root" object. Under the current dir of traced app, * ./btrace<pid>/btrace-class/ directory is created. Under that directory, a file of the * given fileName is created. */ public static void writeXML(Object obj, String fileName) { BTraceRuntime.writeXML(obj, fileName); } /** * Writes a .dot document to persist the tree of the all the transitively reachable objects from * the given "root" object. .dot documents can be viewed by Graphviz application * (www.graphviz.org) Under the current dir of traced app, ./btrace<pid>/btrace-class/ * directory is created. Under that directory, a file of the given fileName is created. * * @since 1.1 */ public static void writeDOT(Object obj, String fileName) { BTraceRuntime.writeDOT(obj, fileName); } } /* * Wraps the OS related BTrace utility methods * @since 1.2 */ public static class Sys { /** * Returns n'th command line argument. null if not available. * * @param n command line argument index * @return n'th command line argument */ public static String $(int n) { return BTraceRuntime.$(n); } /** * Returns a command line argument value for the given key. {@code null} if not available.
* In order to provide a key-value pair on the command line it must have the following syntax - * <key>=<value> * * @param key the argument key * @return the corresponding value or {@code null} */ public static String $(String key) { return BTraceRuntime.$(key); } /** Returns the process id of the currently BTrace'd process. */ public static int getpid() { int pid = -1; try { pid = Integer.parseInt($(0)); } catch (Exception ignored) { } return pid; } /** Returns the number of command line arguments. */ public static int $length() { return BTraceRuntime.$length(); } /** * Exits the BTrace session -- note that the particular client's tracing session exits and not * the observed/traced program! After exit call, the trace action method terminates immediately * and no other probe action method (of that client) will be called after that. * * @param exitCode exit value sent to the client */ public static void exit(int exitCode) { BTraceRuntime.exit(exitCode); } /** * This is same as exit(int) except that the exit code is zero. * * @see #exit(int) */ public static void exit() { exit(0); } /* * Wraps the environment related BTrace utility methods * @since 1.2 */ public static class Env { /** * Gets the system property indicated by the specified key. * * @param key the name of the system property. * @return the string value of the system property, or null if there is no * property with that key. * @throws NullPointerException if key is null. * @throws IllegalArgumentException if key is empty. */ public static String property(String key) { return BTraceRuntime.property(key); } /** * Returns all Sys properties. * * @return the system properties */ public static Properties properties() { return BTraceRuntime.properties(); } /** Prints all Sys properties. */ public static void printProperties() { BTraceRuntime.printMap(properties()); } /** * Gets the value of the specified environment variable. An environment variable is a * system-dependent external named value. * * @param name the name of the environment variable * @return the string value of the variable, or null if the variable is not * defined in the system environment * @throws NullPointerException if name is null */ public static String getenv(String name) { return BTraceRuntime.getenv(name); } /** * Returns an unmodifiable string map view of the current system environment. The environment * is a system-dependent mapping from names to values which is passed from parent to child * processes. * * @return the environment as a map of variable names to values */ public static Map getenv() { return BTraceRuntime.getenv(); } /** Prints all system environment values. */ public static void printEnv() { BTraceRuntime.printMap(getenv()); } /** * Returns the number of processors available to the Java virtual machine. * *

This value may change during a particular invocation of the virtual machine. * Applications that are sensitive to the number of available processors should therefore * occasionally poll this property and adjust their resource usage appropriately. * * @return the maximum number of processors available to the virtual machine; never smaller * than one */ public static long availableProcessors() { return Runtime.getRuntime().availableProcessors(); } } /* * Wraps the memory related BTrace utility methods * @since 1.2 */ public static class Memory { // memory usage /** * Returns the amount of free memory in the Java Virtual Machine. Calling the gc * method may result in increasing the value returned by freeMemory. * * @return an approximation to the total amount of memory currently available for future * allocated objects, measured in bytes. */ public static long freeMemory() { return Runtime.getRuntime().freeMemory(); } /** * Returns the total amount of memory in the Java virtual machine. The value returned by this * method may vary over time, depending on the host environment. * *

Note that the amount of memory required to hold an object of any given type may be * implementation-dependent. * * @return the total amount of memory currently available for current and future objects, * measured in bytes. */ public static long totalMemory() { return Runtime.getRuntime().totalMemory(); } /** * Returns the maximum amount of memory that the Java virtual machine will attempt to use. If * there is no inherent limit then the value {@link java.lang.Long#MAX_VALUE} will be * returned. * * @return the maximum amount of memory that the virtual machine will attempt to use, measured * in bytes */ public static long maxMemory() { return Runtime.getRuntime().maxMemory(); } /** Returns heap memory usage */ public static MemoryUsage heapUsage() { return BTraceRuntime.heapUsage(); } /** Returns non-heap memory usage */ public static MemoryUsage nonHeapUsage() { return BTraceRuntime.nonHeapUsage(); } /** * Returns the amount of memory in bytes that the Java virtual machine initially requests from * the operating system for memory management. */ public static long init(MemoryUsage mu) { return mu.getInit(); } /** * Returns the amount of memory in bytes that is committed for the Java virtual machine to * use. This amount of memory is guaranteed for the Java virtual machine to use. */ public static long committed(MemoryUsage mu) { return mu.getCommitted(); } /** * Returns the maximum amount of memory in bytes that can be used for memory management. This * method returns -1 if the maximum memory size is undefined. */ public static long max(MemoryUsage mu) { return mu.getMax(); } /** Returns the amount of used memory in bytes. */ public static long used(MemoryUsage mu) { return mu.getUsed(); } /** Returns the approximate number of objects for which finalization is pending. */ public static long finalizationCount() { return BTraceRuntime.finalizationCount(); } /** * Dump the snapshot of the Java heap to a file in hprof binary format. Only the live objects * are dumped. Under the current dir of traced app, ./btrace<pid>/btrace-class/ * directory is created. Under that directory, a file of given fileName is created. * * @param fileName name of the file to which heap is dumped */ public static void dumpHeap(String fileName) { dumpHeap(fileName, true); } /** * Dump the snapshot of the Java heap to a file in hprof binary format. Under the current dir * of traced app, ./btrace<pid>/btrace-class/ directory is created. Under that * directory, a file of given fileName is created. * * @param fileName name of the file to which heap is dumped * @param live flag that tells whether only live objects are to be dumped or all objects are * to be dumped. */ public static void dumpHeap(String fileName, boolean live) { BTraceRuntime.dumpHeap(fileName, live); } /** * Runs the garbage collector. * *

Calling the gc method suggests that the Java Virtual Machine expend effort * toward recycling unused objects in order to make the memory they currently occupy available * for quick reuse. When control returns from the method call, the Java Virtual Machine has * made a best effort to reclaim space from all discarded objects. This method calls Sys.gc() * to perform GC. */ public static void gc() { java.lang.System.gc(); } /** * Returns the total amount of time spent in GarbageCollection up to this point since the * application was started. * * @return Returns the amount of overall time spent in GC */ public static long getTotalGcTime() { return BTraceRuntime.getTotalGcTime(); } /** * Returns an overview of available memory pools
* It is possible to provide a text format the overview will use * * @param poolFormat The text format string to format the overview.
* Exactly 5 arguments are passed to the format function.
* The format defaults to ";%1$s;%2$d;%3$d;%4$d;%5$d;Memory]" * @return Returns the formatted value of memory pools overview * @since 1.2 */ public static String getMemoryPoolUsage(String poolFormat) { return BTraceRuntime.getMemoryPoolUsage(poolFormat); } /** * Runs the finalization methods of any objects pending finalization. * *

Calling this method suggests that the Java Virtual Machine expend effort toward running * the finalize methods of objects that have been found to be discarded but whose * finalize methods have not yet been run. When control returns from the method * call, the Java Virtual Machine has made a best effort to complete all outstanding * finalizations. This method calls Sys.runFinalization() to run finalization. */ public static void runFinalization() { java.lang.System.runFinalization(); } } /* * Wraps the VM related BTrace utility methods * @since 1.2 */ public static class VM { /** * Returns the input arguments passed to the Java virtual machine which does not include the * arguments to the main method. This method returns an empty list if there is no * input argument to the Java virtual machine. * *

Some Java virtual machine implementations may take input arguments from multiple * different sources: for examples, arguments passed from the application that launches the * Java virtual machine such as the 'java' command, environment variables, configuration * files, etc. * *

Typically, not all command-line options to the 'java' command are passed to the Java * virtual machine. Thus, the returned input arguments may not include all command-line * options. * * @return a list of String objects; each element is an argument passed to the Java * virtual machine. */ public static List vmArguments() { return BTraceRuntime.getInputArguments(); } /** * Prints VM input arguments list. * * @see #vmArguments */ public static void printVmArguments() { println(vmArguments()); } /** * Returns the Java virtual machine implementation version. This method is equivalent to * Sys.getProperty("java.vm.version")}. * * @return the Java virtual machine implementation version. */ public static String vmVersion() { return BTraceRuntime.getVmVersion(); } /** * Tests if the Java virtual machine supports the boot class path mechanism used by the * bootstrap class loader to search for class files. * * @return true if the Java virtual machine supports the class path mechanism; * false otherwise. */ public static boolean isBootClassPathSupported() { return BTraceRuntime.isBootClassPathSupported(); } /** * Returns the boot class path that is used by the bootstrap class loader to search for class * files. * *

Multiple paths in the boot class path are separated by the path separator character of * the platform on which the Java virtual machine is running. * *

A Java virtual machine implementation may not support the boot class path mechanism for * the bootstrap class loader to search for class files. The {@link #isBootClassPathSupported} * method can be used to determine if the Java virtual machine supports this method. * * @return the boot class path. * @throws java.lang.UnsupportedOperationException if the Java virtual machine does not * support this operation. */ public static String bootClassPath() { return BTraceRuntime.getBootClassPath(); } /** * Returns the Java class path that is used by the system class loader to search for class * files. This method is equivalent to Sys.getProperty("java.class.path"). * * @return the Java class path. */ public static String classPath() { return Sys.Env.property("java.class.path"); } /** * Returns the Java library path. This method is equivalent to * Sys.getProperty("java.library.path"). * *

Multiple paths in the Java library path are separated by the path separator character of * the platform of the Java virtual machine being monitored. * * @return the Java library path. */ public static String libraryPath() { return Sys.Env.property("java.library.path"); } /** * Returns the current number of live threads including both daemon and non-daemon threads. * * @return the current number of live threads. */ public static long threadCount() { return BTraceRuntime.getThreadCount(); } /** * Returns the peak live thread count since the Java virtual machine started or peak was * reset. * * @return the peak live thread count. */ public static long peakThreadCount() { return BTraceRuntime.getPeakThreadCount(); } /** * Returns the total number of threads created and also started since the Java virtual machine * started. * * @return the total number of threads started. */ public static long totalStartedThreadCount() { return BTraceRuntime.getTotalStartedThreadCount(); } /** * Returns the current number of live daemon threads. * * @return the current number of live daemon threads. */ public static long daemonThreadCount() { return BTraceRuntime.getDaemonThreadCount(); } /** * Returns the system load average for the last minute * * @return the system load average for the last minute */ public static double systemLoadAverage() { return BTraceRuntime.getSystemLoadAverage(); } /** * Returns the CPU time used by the process on which the Java virtual machine is running in * nanoseconds. * * @return the CPU time used by the process on which the JVM is running in nanoseconds. * Returns -1 if this operation is not supported on this platform */ public static long processCPUTime() { return BTraceRuntime.getProcessCPUTime(); } /** * Returns the start time of the Java virtual machine in milliseconds. This method returns the * approximate time when the Java virtual machine started. * * @return start time of the Java virtual machine in milliseconds. */ public static long vmStartTime() { return BTraceRuntime.vmStartTime(); } /** * Returns the uptime of the Java virtual machine in milliseconds. * * @return uptime of the Java virtual machine in milliseconds. */ public static long vmUptime() { return BTraceRuntime.vmUptime(); } /** * Returns the total CPU time for the current thread in nanoseconds. The returned value is of * nanoseconds precision but not necessarily nanoseconds accuracy. If the implementation * distinguishes between user mode time and system mode time, the returned CPU time is the * amount of time that the current thread has executed in user mode or system mode. */ public static long currentThreadCpuTime() { return BTraceRuntime.getCurrentThreadCpuTime(); } /** * Returns the CPU time that the current thread has executed in user mode in nanoseconds. The * returned value is of nanoseconds precision but not necessarily nanoseconds accuracy. */ public static long currentThreadUserTime() { return BTraceRuntime.getCurrentThreadUserTime(); } } } /* * Wraps the jvmstat counters related BTrace utility methods * @since 1.2 */ public static class Counters { /** accessing jvmstat (perf) int counter */ public static long perfInt(String name) { return BTraceRuntime.perfInt(name); } /** accessing jvmstat (perf) long counter */ public static long perfLong(String name) { return BTraceRuntime.perfLong(name); } /** accessing jvmstat (perf) String counter */ public static String perfString(String name) { return BTraceRuntime.perfString(name); } } /* * Wraps the dtrace related BTrace utility methods * @since 1.2 */ public static class D { /** * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. * * @see #dtraceProbe(String, String, int, int) */ public static int probe(String str1, String str2) { return probe(str1, str2, -1, -1); } /** * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. * * @see #dtraceProbe(String, String, int, int) */ public static int probe(String str1, String str2, int i1) { return probe(str1, str2, i1, -1); } /** * BTrace to DTrace communication channel. Raise DTrace USDT probe from BTrace. * * @param str1 first String param to DTrace probe * @param str2 second String param to DTrace probe * @param i1 first int param to DTrace probe * @param i2 second int param to DTrace probe */ public static int probe(String str1, String str2, int i1, int i2) { return BTraceRuntime.dtraceProbe(str1, str2, i1, i2); } } /** * Support for JFR integration. * * @since 2.1.0 */ public static final class Jfr { /** * Create a new event instance using the factory field annotated by {@linkplain * org.openjdk.btrace.core.annotations.Event} * * @param eventFactory the event factory * @return new event instance */ public static JfrEvent prepareEvent(JfrEvent.Factory eventFactory) { return eventFactory.newEvent(); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, byte fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, char fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, short fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, int fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, long fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, float fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, double fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, boolean fieldValue) { event.withValue(fieldName, fieldValue); } /** * Set an event field value * * @param event event instance * @param fieldName field name * @param fieldValue field value */ public static void setEventField(JfrEvent event, String fieldName, String fieldValue) { event.withValue(fieldName, fieldValue); } /** * Check whether the event should be committed. * * @see Event#shouldCommit() * @param event event to check * @return {@literal true} if the event should be committed */ public static boolean shouldCommit(JfrEvent event) { return event.shouldCommit(); } /** * Commit the event * * @see Event#commit() * @param event the event to commit */ public static void commit(JfrEvent event) { event.commit(); } public static void begin(JfrEvent event) { event.begin(); } public static void end(JfrEvent event) { event.end(); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/CircularBuffer.java ================================================ package org.openjdk.btrace.core; public final class CircularBuffer { private final T[] elements; private final int size; private long readIndex = 0; private long writeIndex = -1; private int length = 0; @SuppressWarnings("unchecked") public CircularBuffer(int size) { this.size = size; elements = (T[]) new Object[size]; } public void add(T element) { int newIndex = (int) (++writeIndex) % size; elements[newIndex] = element; int nextIndex = (newIndex + 1) % size; if (elements[nextIndex] != null) { readIndex = nextIndex; } if (++length > size) { length = size; } } public boolean forEach(Function functor) { int cntr = 0; while (cntr < size && writeIndex >= readIndex) { if (functor.apply(elements[(int) readIndex % size])) { readIndex++; if (--length < 0) { length = 0; } } else { return false; } cntr++; } return true; } public boolean doNext(Function nextWork) { if (writeIndex >= readIndex) { if (nextWork.apply(elements[(int) readIndex % size])) { readIndex++; return true; } } return false; } public int getLength() { return length; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/DebugSupport.java ================================================ /* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import java.io.File; import java.io.FileOutputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.slf4j.Logger; import org.slf4j.impl.SimpleLogger; /** * Centralized support for logging various debug information. * * @author Jaroslav Bachorik */ public final class DebugSupport { public static void initLoggers(boolean debug, Logger logger) { String logFile = System.getProperty("org.slf4j.simpleLogger.logFile"); System.setProperty("org.slf4j.simpleLogger.logFile", logFile != null ? logFile : "System.out"); String defaultLevel = System.getProperty("org.slf4j.simpleLogger.log.org.openjdk.btrace", "info"); System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", debug ? "debug" : defaultLevel); try { Method mthd = SimpleLogger.class.getDeclaredMethod("init"); mthd.setAccessible(true); mthd.invoke(null); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { System.err.println("[btrace] Unable to reload logger config"); } if (logger != null) { try { Field fld = logger.getClass().getDeclaredField("currentLogLevel"); fld.setAccessible(true); fld.set( logger, debug ? 10 : 20); // 10 is the 'debug' level for SLF4J SimpleLogger, 20 is the info level } catch (NoSuchFieldException | IllegalAccessException e) { System.err.println("[btrace] Unable to set debug log level"); e.printStackTrace(System.err); } } } private final SharedSettings settings; public DebugSupport(SharedSettings s) { settings = s != null ? s : SharedSettings.GLOBAL; } public boolean isDebug() { return settings.isDebug(); } public boolean isDumpClasses() { return settings.isDumpClasses(); } public String getDumpClassDir() { return settings.getDumpDir(); } public void dumpClass(String className, byte[] code) { if (settings.isDumpClasses()) { try { className = className.replace(".", File.separator).replace("/", File.separator); int index = className.lastIndexOf(File.separatorChar); StringBuilder buf = new StringBuilder(); if (!settings.getDumpDir().equals(".")) { buf.append(settings.getDumpDir()); buf.append(File.separatorChar); } String dir = buf.toString(); if (index != -1) { dir += className.substring(0, index); } new File(dir).mkdirs(); String file; if (index != -1) { file = className.substring(index + 1); } else { file = className; } file += ".class"; new File(dir).mkdirs(); File out = new File(dir, file); try (FileOutputStream fos = new FileOutputStream(out)) { fos.write(code); } } catch (Exception exp) { exp.printStackTrace(); } } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/Function.java ================================================ package org.openjdk.btrace.core; public interface Function { R apply(T value); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/HandlerRepository.java ================================================ package org.openjdk.btrace.core; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; /** * A bridge interface between a handler repository implementation and the invoke dynamic bootstrap * class doing the handler lookup. */ @FunctionalInterface public interface HandlerRepository { MethodHandle resolveHandler(String probeName, String handlerName, MethodType handlerType); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/Messages.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; public final class Messages { private static final ResourceBundle messages; static { messages = ResourceBundle.getBundle( "org.openjdk.btrace.core.messages", Locale.getDefault(), Messages.class.getClassLoader()); } private Messages() {} public static String get(String key) { return messages.getString(key); } public static String format(String key, Object... args) { return MessageFormat.format(get(key), args); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/MethodID.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * A factory class for shared method ids * * @author Jaroslav Bachorik */ public class MethodID { static final AtomicInteger lastMethodId = new AtomicInteger(1); // Use a concurrent map to ensure thread-safe access without external synchronization private static final ConcurrentHashMap methodIds = new ConcurrentHashMap<>(); /** * Generates a unique method id based on the provided method tag * * @param methodTag The tag used to distinguish between methods * @return An ID belonging to the provided method tag */ public static int getMethodId(String methodTag) { return methodIds.computeIfAbsent(methodTag, k -> lastMethodId.getAndIncrement()); } public static int getMethodId(String className, String method, String desc) { return getMethodId(className + "#" + method + "#" + desc); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/PrefixMap.java ================================================ /* * Copyright (c) 2017, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.core; import java.util.HashMap; import java.util.Map; /** Simplified trie-based prefix map */ public class PrefixMap { private final Node root = new Node(); public void add(CharSequence val) { Node n = root; for (int i = 0; i < val.length(); i++) { char ch = val.charAt(i); Node child = n.getReferencedNode(ch); if (child == null) { child = new Node(); n.addReferencedNode(ch, child); } n = child; } n.setValue(val); } public boolean contains(CharSequence val) { Node n = root; for (int i = 0; i < val.length(); i++) { char ch = val.charAt(i); Node child = n.getReferencedNode(ch); if (child == null) { return false; } if (child.value != null) { return true; } n = child; } return false; } private static final class Node { private final Map refs = new HashMap<>(); private CharSequence value; public Node() { value = null; } public Node getReferencedNode(char ch) { return refs.get(ch); } public void addReferencedNode(char ch, Node n) { refs.putIfAbsent(ch, n); } public void setValue(CharSequence val) { value = val; } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/Profiler.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import org.openjdk.btrace.core.annotations.Property; /** * Profiler is a highly specialized aggregation-like data collector optimized for high-speed * collection of the application execution call tree data.
*
* It is exposable as MBean via {@linkplain Property} annotation * * @author Jaroslav Bachorik * @since 1.2 */ public abstract class Profiler { /** * This property exposes the time of creating this particular {@linkplain Profiler} instance.
* The unit is milliseconds. */ public final long START_TIME; /** Creates a new {@linkplain Profiler} instance */ public Profiler() { START_TIME = System.currentTimeMillis(); } /** * Records the event of entering an execution unit (eg. method)
* Must be paired with a call to {@linkplain Profiler#recordExit(java.lang.String, long) } with * the same blockName, eventually * * @param blockName The execution unit identifier (eg. method FQN) */ public abstract void recordEntry(String blockName); /** * Records the event of exiting an execution unit (eg. method)
* Must be preceded by a call to {@linkplain Profiler#recordEntry(java.lang.String) } with the * same blockName * * @param blockName The execution unit identifier (eg. method FQN) * @param duration Invocation duration in nanoseconds */ public abstract void recordExit(String blockName, long duration); /** * Creates an immutable snapshot of the collected profiling data * * @return Returns the immutable {@linkplain Snapshot} instance */ public final Snapshot snapshot() { return snapshot(false); } /** * Creates an immutable snapshot of the collected profiling data.
* Makes it possible to reset the profiler after creating the snapshot, eventually * * @param reset Signals the profiler to perform reset right after getting the snapshot (in an * atomic transaction) * @return Returns the immutable {@linkplain Snapshot} instance */ public abstract Snapshot snapshot(boolean reset); /** Resets all the collected data */ public abstract void reset(); /** Helper interface to make accessing a {@linkplain Profiler} as an MBean type safe. */ public interface MBeanValueProvider { Snapshot getMBeanValue(); } /** * Record represents an atomic unit in the application execution call tree * * @since 1.2 */ public static final class Record { public static final Comparator COMPARATOR = (o1, o2) -> { if (o1 == null && o2 != null) return 1; if (o1 != null && o2 == null) return -1; if (o1 == null && o2 == null) return 0; return o1.blockName.compareTo(o2.blockName); }; public final String blockName; public long wallTime = 0, wallTimeMax = 0, wallTimeMin = Long.MAX_VALUE; public long selfTime = 0, selfTimeMax = 0, selfTimeMin = Long.MAX_VALUE; public long invocations = 1; public boolean onStack = false; public Record referring = null; public Record(String blockName) { this.blockName = blockName; } public Record duplicate() { Record r = new Record(blockName); r.invocations = invocations; r.selfTime = selfTime; r.selfTimeMax = selfTimeMax; r.selfTimeMin = selfTimeMin; r.wallTime = wallTime; r.wallTimeMax = wallTimeMax; r.wallTimeMin = wallTimeMin; return r; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Record other = (Record) obj; if (!Objects.equals(blockName, other.blockName)) { return false; } if (wallTime != other.wallTime) { return false; } if (selfTime != other.selfTime) { return false; } return invocations == other.invocations; } @Override public int hashCode() { int hash = 7; hash = 17 * hash + (blockName != null ? blockName.hashCode() : 0); hash = 17 * hash + (int) (wallTime ^ (wallTime >>> 32)); hash = 17 * hash + (int) (selfTime ^ (selfTime >>> 32)); hash = 17 * hash + (int) (invocations ^ (invocations >>> 32)); return hash; } @Override public String toString() { return "Record{\n" + "\tblockName=" + blockName + "\n," + "\twallTime=" + wallTime + ",\n" + "\twallTime.min=" + wallTimeMin + ",\n" + "\twallTime.max=" + wallTimeMax + ",\n" + "\tselfTime=" + selfTime + ",\n" + "\tselfTime.min=" + selfTimeMin + ",\n" + "\tselfTime.max=" + selfTimeMax + ",\n" + "\tinvocations=" + invocations + ",\nonStack=" + onStack + '}'; } } /** * Snapshot is an immutable image of the current profiling data collected by the {@linkplain * Profiler}
*
* It is created by calling {@linkplain Profiler#snapshot()} method * * @since 1.2 */ public static final class Snapshot { public final long timeStamp; public final long timeInterval; public final Record[] total; public Snapshot(Record[] data, long startTs, long stopTs) { timeStamp = stopTs; timeInterval = stopTs - startTs; total = data; } public List getGridData() { List rslt = new ArrayList<>(); Object[] titleRow = { "Block", "Invocations", "SelfTime.Total", "SelfTime.Avg", "SelfTime.Min", "SelfTime.Max", "WallTime.Total", "WallTime.Avg", "WallTime.Min", "WallTime.Max" }; rslt.add(titleRow); for (Record r : total) { if (r != null) { Object[] row = { r.blockName, r.invocations, r.selfTime, r.selfTime / r.invocations, r.selfTimeMin < Long.MAX_VALUE ? r.selfTimeMin : "N/A", r.selfTimeMax > 0 ? r.selfTimeMax : "N/A", r.wallTime, r.wallTime / r.invocations, r.wallTimeMin < Long.MAX_VALUE ? r.wallTimeMin : "N/A", r.wallTimeMax > 0 ? r.wallTimeMax : "N/A" }; rslt.add(row); } } return rslt; } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/SharedSettings.java ================================================ /* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; import java.util.Map; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.PermissionSet; /** * @author Jaroslav Bachorik */ public final class SharedSettings { public static final String DEBUG_KEY = "debug"; public static final String DUMP_DIR_KEY = "dumpDir"; @Deprecated public static final String UNSAFE_KEY = "unsafe"; public static final String TRUSTED_KEY = "trusted"; public static final String TRACK_RETRANSFORMS_KEY = "trackRetransforms"; public static final String PROBE_DESC_PATH_KEY = "probeDescPath"; public static final String STATSD_HOST_KEY = "statsdHost"; public static final String STATSD_PORT_KEY = "statsdPort"; public static final String FILEROLL_INTERVAL_KEY = "fileRollMilliseconds"; public static final String FILEROLL_MAXROLLS_KEY = "fileRollMaxRolls"; public static final String OUTPUT_FILE_KEY = "scriptOutputFile"; public static final String OUTPUT_DIR_KEY = "scriptOutputDir"; public static final String GRANT_PERMISSIONS_KEY = "grantPermissions"; public static final String DENY_PERMISSIONS_KEY = "denyPermissions"; public static final String GRANT_ALL_KEY = "grantAll"; public static final SharedSettings GLOBAL = new SharedSettings(); private boolean debug = false; private boolean trusted = false; private boolean trackRetransforms = false; private boolean retransformStartup = true; private String dumpDir = null; private String probeDescPath = "."; private String bootClassPath = ""; private final String systemClassPath = ""; private String statsdHost = null; private int statsdPort = 8125; // default statsd port private int fileRollMilliseconds = Integer.MIN_VALUE; private int fileRollMaxRolls = 5; // default hold max 100 logs private String outputFile; private String scriptDir; private String scriptOutputDir; private String clientName; private PermissionSet grantedPermissions = PermissionSet.empty(); private PermissionSet deniedPermissions = PermissionSet.empty(); private boolean grantAll = false; public void from(Map params) { Boolean b = (Boolean) params.get(DEBUG_KEY); if (b != null) { debug = b; } b = (Boolean) params.get(TRACK_RETRANSFORMS_KEY); if (b != null) { trackRetransforms = b; } b = (Boolean) params.get(UNSAFE_KEY); if (b != null) { trusted = b; } b = (Boolean) params.get(TRUSTED_KEY); if (b != null) { trusted |= b; } String s = (String) params.get(DUMP_DIR_KEY); if (s != null && !s.isEmpty()) { dumpDir = s; } s = (String) params.get(PROBE_DESC_PATH_KEY); if (s != null && !s.isEmpty()) { probeDescPath = s; } s = (String) params.get(Args.BOOT_CLASS_PATH); if (s != null && !s.isEmpty()) { bootClassPath = s; } s = (String) params.get(STATSD_HOST_KEY); if (s != null && !s.isEmpty()) { statsdHost = s; } Integer i = (Integer) params.get(STATSD_PORT_KEY); if (i != null) { statsdPort = i; } i = (Integer) params.get(FILEROLL_INTERVAL_KEY); if (i != null) { fileRollMilliseconds = i; } i = (Integer) params.get(FILEROLL_MAXROLLS_KEY); if (i != null) { fileRollMaxRolls = i; } s = (String) params.get(OUTPUT_FILE_KEY); if (s != null && !s.isEmpty()) { outputFile = s; } s = (String) params.get(OUTPUT_DIR_KEY); if (s != null && !s.isEmpty()) { scriptOutputDir = s; } s = (String) params.get(GRANT_PERMISSIONS_KEY); if (s != null && !s.isEmpty()) { grantedPermissions = parsePermissions(s); } s = (String) params.get(DENY_PERMISSIONS_KEY); if (s != null && !s.isEmpty()) { deniedPermissions = parsePermissions(s); } b = (Boolean) params.get(GRANT_ALL_KEY); if (b != null) { grantAll = b; } } public static PermissionSet parsePermissions(String permissionString) { if (permissionString == null || permissionString.isEmpty()) { return PermissionSet.empty(); } PermissionSet result = PermissionSet.empty(); for (String name : permissionString.split(",")) { String trimmed = name.trim().toUpperCase(); if (!trimmed.isEmpty()) { try { Permission p = Permission.valueOf(trimmed); result = result.with(p); } catch (IllegalArgumentException e) { // Ignore invalid permission names } } } return result; } public void from(SharedSettings other) { clientName = other.clientName; debug = other.debug; dumpDir = other.dumpDir; fileRollMilliseconds = other.fileRollMilliseconds; fileRollMaxRolls = other.fileRollMaxRolls; outputFile = other.outputFile; scriptDir = other.scriptDir; scriptOutputDir = other.scriptOutputDir; probeDescPath = other.probeDescPath; bootClassPath = other.bootClassPath; retransformStartup = other.retransformStartup; statsdHost = other.statsdHost; statsdPort = other.statsdPort; trackRetransforms = other.trackRetransforms; trusted = other.trusted; grantedPermissions = other.grantedPermissions; deniedPermissions = other.deniedPermissions; grantAll = other.grantAll; } public boolean isDebug() { return debug; } public void setDebug(boolean value) { debug = value; } public boolean isDumpClasses() { return dumpDir != null; } @Deprecated /* @deprecated use {@linkplain SharedSettings#isTrusted()} instead */ public boolean isUnsafe() { return trusted; } public boolean isTrusted() { return trusted; } public void setTrusted(boolean value) { trusted = value; } public String getDumpDir() { return dumpDir; } public void setDumpDir(String value) { dumpDir = value; } public boolean isTrackRetransforms() { return trackRetransforms; } public void setTrackRetransforms(boolean value) { trackRetransforms = value; } public String getProbeDescPath() { return probeDescPath; } public void setProbeDescPath(String probeDescPath) { this.probeDescPath = probeDescPath; } public String getBootClassPath() { return bootClassPath; } public void setBootClassPath(String bootClassPath) { this.bootClassPath = bootClassPath; } public String getStatsdHost() { return statsdHost; } public void setStatsdHost(String statsdHost) { this.statsdHost = statsdHost; } public int getStatsdPort() { return statsdPort; } public void setStatsdPort(int statsdPort) { this.statsdPort = statsdPort; } public int getFileRollMilliseconds() { return fileRollMilliseconds; } public void setFileRollMilliseconds(int fileRollMilliseconds) { this.fileRollMilliseconds = fileRollMilliseconds; } public int getFileRollMaxRolls() { return fileRollMaxRolls; } public void setFileRollMaxRolls(int fileRollMaxRolls) { this.fileRollMaxRolls = fileRollMaxRolls; } public boolean isRetransformStartup() { return retransformStartup; } public void setRetransformStartup(boolean val) { retransformStartup = val; } public String getScriptDir() { return scriptDir; } public String getOutputFile() { return outputFile; } public void setOutputFile(String outputFile) { this.outputFile = outputFile; } public String getScriptOutputDir() { return scriptOutputDir; } public void setScriptOutputDir(String scriptOutputDir) { this.scriptOutputDir = scriptOutputDir; } public String getClientName() { return clientName; } public void setClientName(String clientName) { this.clientName = clientName; } public PermissionSet getGrantedPermissions() { return grantedPermissions; } public void setGrantedPermissions(PermissionSet grantedPermissions) { this.grantedPermissions = grantedPermissions; } public PermissionSet getDeniedPermissions() { return deniedPermissions; } public void setDeniedPermissions(PermissionSet deniedPermissions) { this.deniedPermissions = deniedPermissions; } public boolean isGrantAll() { return grantAll; } public void setGrantAll(boolean grantAll) { this.grantAll = grantAll; } /** * Computes the effective permissions based on granted, denied, and grantAll settings. * *

Logic: * *

    *
  • If grantAll is true, all permissions are granted *
  • Otherwise, start with standard permissions (default + standard tier) *
  • Add explicitly granted permissions *
  • Remove explicitly denied permissions *
* * @return the effective permission set */ public PermissionSet getEffectivePermissions() { if (grantAll) { return PermissionSet.all(); } PermissionSet effective = PermissionSet.standard(); for (Permission p : grantedPermissions) { effective = effective.with(p); } for (Permission p : deniedPermissions) { effective = effective.without(p); } return effective; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/VerifierException.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core; /** * Instance of this exception type is thrown by BTrace Verifier when an input .class is not a valid * BTrace program. * * @author A. Sundararajan */ public class VerifierException extends RuntimeException { public VerifierException(String msg) { super(msg); } public VerifierException(Throwable cause) { super(cause); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/BTrace.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Top-level annotation that identifies a BTrace class. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface BTrace { // If this trace is exposed as MXBean, then this tells // the name of the MXBean. String name() default ""; // description of this trace class. String description() default ""; // having "unsafe" set to true the script will be run in unsafe mode @Deprecated /* @deprecated use {@linkplain BTrace#trusted()} instead */ boolean unsafe() default false; boolean trusted() default false; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTrace.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation for BTrace program to associate a D-script with it. D-script is specified as (inline) * String value. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DTrace { /** "one-liner" D-script String */ String value(); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTraceRef.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation for BTrace program to associate a D-script with it. D-script is referred by a relative * or absolute file path. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DTraceRef { /** Locates D-script by a relative or absolute file path. */ String value(); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Duration.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * It is used to mark a probe method argument as the receiver of the duration value
* Applicable only for {@linkplain OnMethod} annotation with {@linkplain Location} value of * {@linkplain Kind#RETURN} or {@linkplain Kind#ERROR} * *

The duration is reported in nanoseconds, using resolution available by OS * * @author Jaroslav Bachorik jaroslav.bachorik@sun.com * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Duration {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Event.java ================================================ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import jdk.jfr.Category; import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.Name; /** * Mark a field as a custom event factory. * *

 *     
 * \@Event(name="myCustomEvent", fields={|@Event.Field(type = FieldType.INT, name = "f1"), |@Event.Field(type = FieldType.STRING, name = "f2"), |@Event.Field(type = FieldType.BOOLEAN, name = "f3")})
 * private static JfrEvent myCustomEvent = null;
 *
 * ...
 * \@OnMethod(...)
 * public static void onprobe() {
 *     JfrEvent event = BTraceUtils.Jfr.prepareEvent(myCustomEvent);
 *     if (event.shouldCommit()) {
 *       BTraceUtils.Jfr.setEventField(event, "f1", 10);
 *       BTraceUtils.Jfr.setEventField(event, "f2", "hello");
 *       BTraceUtils.Jfr.setEventField(event, 2, false);
 *       BTraceUtils.Jfr.commit(event);
 *     }
 * }
 *     
 * 
* * @since 2.1.0 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Event { /** Event field type */ enum FieldType { BYTE("byte"), CHAR("char"), SHORT("short"), INT("int"), LONG("long"), FLOAT("float"), DOUBLE("double"), BOOLEAN("boolean"), STRING("java.lang.String"), CLASS("java.lang.Class"), THREAD("java.lang.Thread"); private final String type; FieldType(String type) { this.type = type; } public String getType() { return type; } } /** Field kind */ enum FieldKind { /** * @see jdk.jfr.Timestamp */ TIMESTAMP, /** * @see jdk.jfr.Timespan */ TIMESPAN, /** * @see jdk.jfr.DataAmount */ DATAAMOUNT, /** * @see jdk.jfr.Frequency */ FREQUENCY, /** * @see jdk.jfr.MemoryAddress */ MEMORYADDRESS, /** * @see jdk.jfr.Percentage */ PERCENTAGE, /** * @see jdk.jfr.BooleanFlag */ BOOLEANFLAG, /** * @see jdk.jfr.Unsigned */ UNSIGNED, /** No additional field kind specification */ NONE } /** Event field definition */ @interface Field { /** Additional field kind */ @interface Kind { FieldKind name(); String value() default ""; } /** * @return field type */ FieldType type(); /** * @return field name */ String name(); /** * @return field label */ String label() default ""; /** * @return field description */ String description() default ""; /** * @return additional field kind */ Kind kind() default @Kind(name = FieldKind.NONE); } /** * Event name * * @return event name * @see Name#value() */ String name(); /** * Event label * * @return event label * @see Label#value() */ String label() default ""; /** * Event description * * @return event description * @see Description#value() */ String description() default ""; /** * Event category * * @return event category * @see Category#value() */ String[] category() default ""; /** * Each event should have a stacktrace associated with it * * @return {@literal true} if each event should have a stacktrace associated with it */ boolean stacktrace() default true; /** * Event fields * * @return event field definitions */ Field[] fields(); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Export.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace fields with this annotation are exposed to out-of-process tools using mechanisms such as * jvmstat. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Export {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Injected.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotates a field as an injected service. * * @author Jaroslav Bachorik */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Injected { // No parameters by default. Construction is auto-detected by the extension bridge. /** * Whether the injected service is optional. * Optional services may fall back to a shim or throwing stub * based on {@link #mode()} or the global property * {@code btrace.extension.shimMode} (shim|throw). */ boolean optional() default false; /** * Per-field override for fallback behavior when {@link #optional()} is true. * If unspecified, the global property {@code btrace.extension.shimMode} * determines whether to inject a shim or a throwing stub. */ InjectionMode mode() default InjectionMode.UNSPECIFIED; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/InjectionMode.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; /** * Fallback behavior for optional injected services. */ public enum InjectionMode { /** Use global property to decide. */ UNSPECIFIED, /** Inject a no-op shim (production). */ SHIM, /** Inject a throwing stub (development). */ THROW } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Kind.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import org.openjdk.btrace.core.types.AnyType; /** * This enum is specified in the Location annotation to specify probe point kind. This enum * identifies various "points" of interest within a Java method's bytecode. * * @author A. Sundararajan */ public enum Kind { /** * * *

Array element load

* *

Unannotated probe handler parameters:

* *
    *
  1. {@code type[]} - the array instance *
  2. {@link int int} - array index *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain * Where#AFTER}) *
*/ ARRAY_GET, /** * * *

Array element store

* *

Unannotated probe handler parameters:

* *
    *
  1. {@code type[]} - the array instance *
  2. {@link int int} - array index *
  3. {@link java.lang.Object Object} - new value *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ ARRAY_SET, /** * * *

Method call

* *

The order and number of unannotated parameters (if provided) must fully match the called * method signature. Instead of specific parameter types one can use {@linkplain AnyType} to match * any type. * *

If the only unannotated parameter is of type {@link AnyType AnyType[]} it will contain the * called method parameters in the order defined by its signature. * *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain TargetInstance} - the target instance of the method call or null if the * method is static *
  • {@linkplain TargetMethodOrField} - the name of the method which is called *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain * Where#AFTER}) *
  • {@linkplain Duration} - the method call duration in nanoseconds (only for {@linkplain * Where#AFTER} *
*/ CALL, /** * * *

Exception catch

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.Throwable Throwable} - caught throwable *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ CATCH, /** * * *

Checkcast

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.String String} - type to cast to *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain TargetInstance} - the casted instance ({@linkplain AnyType}) *
*/ CHECKCAST, /** * * *

Method entry

* *

The order and number of unannotated parameters (if provided) must fully match the probed * method signature. Instead of specific parameter types one can use {@linkplain AnyType} to match * any type. * *

If the only unannotated parameter is of type {@link AnyType AnyType[]} it will contain the * probed method parameters in the order defined by its signature. * *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ ENTRY, /** * * *

"return" because of no-catch

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.Throwable Throwable} - the thrown throwable *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain Duration} - the method call duration in nanoseconds (only for {@linkplain * Where#AFTER} *
*/ ERROR, /** * * *

Getting a field value

* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain TargetInstance} - the field owner instance or null if the field is static *
  • {@linkplain TargetMethodOrField} - the name of the method which is called *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain * Where#AFTER}) *
*/ FIELD_GET, /** * * *

Setting a field value

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.Object Object} - new field value *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that field is * static *
  • {@linkplain TargetInstance} - the field owner instance or null if the field is static *
  • {@linkplain TargetMethodOrField} - the name of the method which is called *
*/ FIELD_SET, /** * * *

instanceof check

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.String String} - type to check against *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain TargetInstance} - the checked instance ({@linkplain AnyType}) *
*/ INSTANCEOF, /** * * *

Source line number

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link int int} - line number *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ LINE, /** * * *

New object created

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.String String} - object type name *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain * Where#AFTER}) *
*/ NEW, /** * * *

New array created

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.String String} - array type name *
  2. {@link int int} - number of dimensions *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain * Where#AFTER}) *
*/ NEWARRAY, /** * * *

Return from method

* *

The order and number of unannotated probe handler parameters (if provided) must fully match * the probed method signature. Instead of specific parameter types one can use {@linkplain * AnyType} to match any type. * *

If the only unannotated parameter is of type {@link AnyType AnyType[]} it will contain the * probed method parameters in the order defined by its signature. * *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain * Where#AFTER}) *
  • {@linkplain Duration} - the method call duration in nanoseconds (only for {@linkplain * Where#AFTER} *
*/ RETURN, /** * * *

Entry into a synchronized block

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.Object Object} - lock object *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ SYNC_ENTRY, /** * * *

Exit from a synchronized block

* *

Unannotated probe handler parameters:

* *
    *
  1. {@link java.lang.Object Object} - lock object *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ SYNC_EXIT, /** * * *

Throwing an exception

* *

Unannotated probe handler parameters:

* *
    *
  1. {@linkplain java.lang.Throwable Throwable} - thrown exception *
* *

Allowed probe handler parameter annotations:

* *
    *
  • {@linkplain ProbeClassName} - the name of the enclosing class *
  • {@linkplain ProbeMethodName} - the name of the enclosing method *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is * static *
*/ THROW } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Level.java ================================================ /* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; /** * Allows specifying a probe handler instrumentation level matching expression. * *

See {@linkplain Level#value()} for the allowed expression syntax. * * @author Jaroslav Bachorik */ public @interface Level { /** * The level check expression.
* Allowed syntax is one of the following * *

    *
  • {@code @Level("NUMBER")} - the same as {@code @Level(">=NUMBER")} *
  • {@code @Level("=NUMBER")} - handler is enabled when instrumentation level equals * NUMBER *
  • {@code @Level(">NUMBER")} - handler is enabled when instrumentation level is greater than * NUMBER *
  • {@code @Level(">=NUMBER")} - handler is enabled when instrumentation level is greater * than or equal to NUMBER *
  • {@code @Level("NUMBER *
  • {@code @Level("<=NUMBER")} - handler is enabled when instrumentation level is less than * or equal to NUMBER *
* *

Where NUMBER is a non-negative integer number. * * @return the level */ String value() default ""; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Location.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * This annotation specifies a particular "location" within a traced/probed java method for BTrace * probe specifications. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Location { /** * Kind of the location. * * @see Kind */ Kind value() default Kind.ENTRY; /** * Specifies where do want to probe with respect to the location of interest. * * @see Where */ Where where() default Where.BEFORE; /** Specifies the fully qualified class name for certain kind of probe locations. */ String clazz() default ""; /** Specifies the method name for certain kind of probe locations. */ String method() default ""; /** * Specifies the field name for Kind.FIELD_SET and Kind.FIELD_GET probes. * * @see Kind#FIELD_GET * @see Kind#FIELD_SET */ String field() default ""; /** * Specifies field or method type for certain kind of probe locations. The type is specified like * in Java source - except the method or field name and parameter names are not included. */ String type() default ""; /** * Specifies the line number for Kind.LINE probes. * * @see Kind#LINE */ int line() default 0; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnError.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace method annotated by this annotation is called when any exception is thrown by any of the * BTrace action methods. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnError {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnEvent.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace methods annotated by this annotation are called when BTrace client sends "event" command. * Client may send an event based on some form of user request to send (like pressing Ctrl-C or a * GUI menu). String value may used as the name of the event. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnEvent { /** The event name. */ String value() default ""; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnExit.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace method annotated by this annotation is called when BTrace "exit(int)" built-in function is * called by some other BTrace action method. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnExit {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnLowMemory.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace methods annotated by this annotation are called when the traced JVM's specified memory * pool exceeds specified threshold size. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnLowMemory { /** The memory pool name. */ String pool(); /** The threashold size to watch for. */ long threshold(); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnMethod.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * * Copyright (c) 2017, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * This annotation specifies a BTrace probe point by specifying a java class (or classes), a method * (or methods in it) and a specific location within it. A BTrace trace action method annotated by * this annotation is called when matching the traced program reaches the specified location. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnMethod { /** * The probed (or traced) class name. This is either fully qualified name of the class or regular * expression within two forward slash characters [like /java\\.awt\\..+/] * or @annotation_of_the_class. i.e., specify a class indirectly as a class annotated by specified * annotation. */ String clazz(); /** * The probed (or traced) method name. This is either the name of the method or regular expression * within two forward slash characters [like /read.+/] or @annotation_of_the_method. i.e., specify * a method indirectly as a method annotated by specified annotation. */ String method() default ""; /** * When set to {@code true} type checks will not involve assignability checks based on class * hierarchy and only exactly matching types will be resolved as assignable. * * @return {@code true} if exact type matching is requested; {@code false} otherwise (default) */ boolean exactTypeMatch() default false; /** * This is method type declaration. This is like Java method declaration but not including method * name, parameter names and throws clause. * * *

Eg. public void myMethod(java.lang.String param) will become void * (java.lang.String) */ String type() default ""; /** Identifies exact "location" or "point" of interest to probe within the set of methods. */ Location location() default @Location; /** * Activate this probe according to instrumentation level. * *

It is possible to define enable/disable the handler according to the current instrumentation * level. Eg. {@code @OnMethod(clazz="class", method="method", enableAt=@Level(">1")} * *

The developer must make sure that all the handlers which are interconnected in any way (eg. * method entry/exit) will be enabled/disabled at a compatible instrumentation level. * * @return The instrumentation level (default {@code @Level("0")}) * @see Level * @see org.openjdk.btrace.core.BTraceUtils#getInstrumentationLevel() * @since 1.3.4 */ Level enableAt() default @Level; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnProbe.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * This annotation is used to specify a BTrace probe point in an abstract fashion instead of * specifying the class and the method names. This abstract namespace and (local) name are mapped to * one or more concrete probe points by the BTrace agent at runtime. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnProbe { /** * Namespace for this abstract (a.k.a logical) probe point. Namespace follows java package name * conventions. */ String namespace(); /** Name within the namespace. Identifies a probe uniquely within the namespace. */ String name(); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnTimer.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace methods annotated by this annotation are called when a timer reaches the specified period * value. This can be used to run periodic tracing actions. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnTimer { /** * Time period of the timer in milliseconds. Defaults to 1 second * * @return timer period */ long value() default 1000L; /** * If specified the timer period will be taken from btrace arguments. The format is Ant-like * property reference - eg. {@code from = "${timer}"} * * @return btrace argument name holding timer period * @since 1.3.11 */ String from() default ""; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/PeriodicEvent.java ================================================ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import jdk.jfr.Category; import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.Name; import jdk.jfr.Period; /** * Mark a method as a JFR periodic event handler. * *

 *     
 * \@PeriodicEvent(name="periodicEvent", fields="boolean hit", period="eachChunk")
 * public static void periodicEventHandler(JfrEvent periodic) {
 *     BTraceUtils.Jfr.setEventField(periodic, "hit", true);
 *     BTraceUtils.Jfr.commit(periodic);
 * }
 *     
 * 
* * @since 2.1.0 */ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface PeriodicEvent { /** * Event name * * @return event name * @see Name#value() */ String name(); /** * Event label * * @return event label * @see Label#value() */ String label() default ""; /** * Event description * * @return event description * @see Description#value() */ String description() default ""; /** * Event category * * @return event category * @see Category#value() */ String[] category() default ""; /** * Event fields * * @return event field definitions */ Event.Field[] fields(); /** * Event period * * @return event period * @see Period#value() */ String period() default "eachChunk"; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeClassName.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * It is used to mark a probe method argument as the receiver of the probe target class name
* Applicable only for {@linkplain OnMethod} annotation * * @author Jaroslav Bachorik jaroslav.bachorik@sun.com * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ProbeClassName {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeMethodName.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * It is used to mark a probe method argument as the receiver of the probe target class name
* Applicable only for {@linkplain OnMethod} annotation * * @author Jaroslav Bachorik jaroslav.bachorik@sun.com * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ProbeMethodName { /** Flag indicating whether a fully qualified name (FQN) or a simple method name should be used */ boolean fqn() default false; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Property.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * BTrace fields with this annotation are exposed as attributes of the dynamic JMX bean that wraps * the BTrace class. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Property { // by default, the name of the attribute is same as the name // of the field of the BTrace class. String name() default ""; // description of this attribute String description() default ""; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Return.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marks a method parameter as the one that should contain the return value * * @author Jaroslav Bachorik */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Return {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Sampled.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marks an {@linkplain OnMethod} handler as a sampled one. When a handler is sampled not all events * will be traced - only a statistical sample with the given mean. * *

By default an adaptive sampling is used. BTrace will increase or decrease the number of * invocations between samples to keep the mean time window, thus decreasing the overall overhead. * * @author Jaroslav Bachorik */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface Sampled { int MEAN_DEFAULT = 10; /** * The sampler kind * * @return The sampler kind */ Sampler kind() default Sampler.Const; /** * The sampler mean. * *

For {@code Sampler.Const} it is the average number of events between samples.
* For {@code Sampler.Adaptive} it is the average time (in ns) between samples
* * @return The sampler mean */ int mean() default MEAN_DEFAULT; /** * Specifies the sampler kind * *

    *
  • {@code None} - no sampling *
  • {@code Const} - keeps the average number of events between samples *
  • {@code Adaptive} - increases or decreases the average number of events between samples to * lower overhead *
*/ enum Sampler { /** No Sampling */ None, /** Keeps the average number of events between samples */ Const, /** Increases or decreases the average number of events between samples to lower overhead */ Adaptive } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Self.java ================================================ /* * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marks a method parameter as the one that should contain *this* instance * * @author Jaroslav Bachorik */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Self {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TLS.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation for thread-local BTrace fields. BTrace fields annotated with this annotation are * stored in thread local storage. Field get/set are transparently converted to thread local get and * set respectively. * *

Important!!!

* * It is not possible to access the data stored in the thread local storage from any other handler * than {@linkplain OnMethod} * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface TLS {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetInstance.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * It is used to mark a probe method argument as the receiver of called instance with {@linkplain * Location} value of {@linkplain Kind#CALL}, {@linkplain Kind#FIELD_GET} or {@linkplain * Kind#FIELD_SET}. * * @author Jaroslav Bachorik * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TargetInstance {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetMethodOrField.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * It is used to mark a probe method argument as the receiver of called method name in {@linkplain * Location} = {@linkplain Kind#CALL} * * @author Jaroslav Bachorik * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TargetMethodOrField { /** * Flag indicating whether a fully qualified name (FQN) or a simple method/field name should be * used */ boolean fqn() default false; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Where.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.annotations; /** * This enum is specified in the Location annotation to specify whether a probe point is after or * before specific point of interest. * * @author A. Sundararajan */ public enum Where { /** after the location of interest */ AFTER, /** before the location of interest */ BEFORE } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/BinaryWireProtocol.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.openjdk.btrace.core.comm.v2.BinaryCommand; import org.openjdk.btrace.core.comm.v2.BinaryWireIO; import org.openjdk.btrace.core.comm.v2.CommandAdapter; /** * Wire protocol implementation using custom binary format (V2 protocol). * *

This is the high-performance binary protocol for BTrace that provides significant improvements * over Java serialization: * *

    *
  • 3-6x faster serialization/deserialization *
  • 2-5x smaller payload sizes (10-100x with compression) *
  • Automatic compression for payloads > 1KB *
  • Zero-copy optimizations where possible *
  • Thread-safe with ReentrantLock *
  • Magic byte prefix: 0x42 0x54 0x52 0x32 ("BTR2") *
* *

This implementation uses {@link BinaryWireIO} for binary serialization and {@link * CommandAdapter} to convert between V1 Command objects and V2 BinaryCommand objects, providing a * transparent bridge for existing code. */ public class BinaryWireProtocol implements WireProtocol { private final InputStream inputStream; private final OutputStream outputStream; private volatile boolean closed = false; /** * Creates a new BinaryWireProtocol instance. * *

Note: The input stream should already be positioned after the V2 magic bytes if protocol * negotiation was performed. * * @param inputStream the input stream to read commands from * @param outputStream the output stream to write commands to * @throws IOException if protocol initialization fails */ public BinaryWireProtocol(InputStream inputStream, OutputStream outputStream) throws IOException { this.inputStream = inputStream; this.outputStream = outputStream; } @Override public Command read() throws IOException { if (closed) { throw new IOException("Protocol is closed"); } // Read binary command BinaryCommand binaryCmd = BinaryWireIO.read(inputStream); // Convert to V1 Command for transparent integration return CommandAdapter.toBtraceCommand(binaryCmd); } @Override public void write(Command command) throws IOException { if (closed) { throw new IOException("Protocol is closed"); } // Convert V1 Command to binary command BinaryCommand binaryCmd = CommandAdapter.toBinaryCommand(command); // Write binary command BinaryWireIO.write(outputStream, binaryCmd); } @Override public void flush() throws IOException { if (!closed) { outputStream.flush(); } } @Override public ProtocolVersion getVersion() { return ProtocolVersion.V2; } @Override public void close() throws IOException { if (closed) { return; } closed = true; IOException exception = null; // Close output stream first to flush any pending data try { outputStream.close(); } catch (IOException e) { exception = e; } // Then close input stream try { inputStream.close(); } catch (IOException e) { if (exception == null) { exception = e; } else { exception.addSuppressed(e); } } if (exception != null) { throw exception; } } /** * Returns the underlying input stream. * *

This method is provided for direct access to the stream if needed for advanced use cases. * * @return the InputStream */ public InputStream getInputStream() { return inputStream; } /** * Returns the underlying output stream. * *

This method is provided for direct access to the stream if needed for advanced use cases. * * @return the OutputStream */ public OutputStream getOutputStream() { return outputStream; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/Command.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; public abstract class Command implements Serializable { public static final byte ERROR = 0; public static final byte EVENT = 1; public static final byte EXIT = 2; public static final byte INSTRUMENT = 3; public static final byte MESSAGE = 4; public static final byte RENAME = 5; public static final byte STATUS = 6; public static final byte NUMBER_MAP = 7; public static final byte STRING_MAP = 8; public static final byte NUMBER = 9; public static final byte GRID_DATA = 10; public static final byte RETRANSFORMATION_START = 11; public static final byte RETRANSFORM_CLASS = 12; public static final byte SET_PARAMS = 13; public static final byte LIST_PROBES = 14; public static final byte DISCONNECT = 15; public static final byte RECONNECT = 16; public static final byte LIST_FAILED_EXTENSIONS = 17; public static final byte FIRST_COMMAND = ERROR; public static final byte LAST_COMMAND = LIST_FAILED_EXTENSIONS; @SuppressWarnings("RedundantThrows") public static final Command NULL = new Command() { @Override protected void write(ObjectOutput out) throws IOException {} @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException {} }; protected byte type; private boolean urgent; protected Command(byte type) { this(type, true); } protected Command(byte type, boolean urgent) { if (type < FIRST_COMMAND || type > LAST_COMMAND) { throw new IllegalArgumentException(); } this.type = type; this.urgent = urgent; } private Command() { type = -1; urgent = true; } protected abstract void write(ObjectOutput out) throws IOException; protected abstract void read(ObjectInput in) throws IOException, ClassNotFoundException; public final byte getType() { return type; } public final boolean isUrgent() { return urgent; } final void setUrgent() { urgent = true; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/CommandListener.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; /** * Callback interface called to notify wire protocol commands. * * @author A. Sundararajan */ public interface CommandListener { void onCommand(Command cmd) throws IOException; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/DataCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; /** * Command that carries arbitrary "result/output" data. * * @author A. Sundararajan */ public abstract class DataCommand extends Command implements PrintableCommand { protected String name; public DataCommand(byte type, String name, boolean urgent) { super(type, urgent); this.name = name; } public String getName() { return name; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/DisconnectCommand.java ================================================ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; /** * @since WireIO v.1 */ public final class DisconnectCommand extends Command implements PrintableCommand { private String probeId = ""; public DisconnectCommand() { super(Command.DISCONNECT, true); } public DisconnectCommand(String probeId) { super(Command.DISCONNECT, true); this.probeId = probeId; } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(probeId); } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { probeId = in.readUTF(); } public void setProbeId(String probeId) { this.probeId = probeId; } public String getProbeId() { return probeId; } @Override public void print(PrintWriter out) { out.println("BTrace Probe: " + probeId); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ErrorCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; public class ErrorCommand extends Command implements PrintableCommand { private Throwable cause; public ErrorCommand(Throwable cause) { super(ERROR, true); this.cause = cause; } protected ErrorCommand() { this(null); } @Override protected void write(ObjectOutput out) throws IOException { out.writeObject(cause); } @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { Object obj = in.readObject(); if (obj != null && !(obj instanceof Throwable)) { throw new IOException( "Invalid data type: expected Throwable, got " + obj.getClass().getName()); } cause = (Throwable) obj; } public Throwable getCause() { return cause; } @Override public void print(PrintWriter out) { out.append("! ERROR\n"); Throwable cause = getCause(); if (cause != null) { cause.printStackTrace(out); } else { out.append("(No exception information available)\n"); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/EventCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class EventCommand extends Command { private String event; public EventCommand(String event) { super(EVENT, true); this.event = event; } protected EventCommand() { this(null); } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(event); } @Override protected void read(ObjectInput in) throws ClassNotFoundException, IOException { event = in.readUTF(); } public String getEvent() { return event; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ExitCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class ExitCommand extends Command { private int exitCode; public ExitCommand(int exitCode) { super(EXIT, true); this.exitCode = exitCode; } protected ExitCommand() { this(0); } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(exitCode); } @Override protected void read(ObjectInput in) throws IOException { exitCode = in.readInt(); } public int getExitCode() { return exitCode; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/GridDataCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; import java.io.StringWriter; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * A data command that holds tabular data. * *

The elements contained within the grid must be of type Number or String. * * @author Christian Glencross */ public class GridDataCommand extends DataCommand { private static final Pattern INDEX_PATTERN = Pattern.compile("%(\\d)+\\$"); private static final HashMap, String> typeFormats = new HashMap<>(); static { typeFormats.put(Integer.class, "%15d"); typeFormats.put(Short.class, "%15d"); typeFormats.put(Byte.class, "%15d"); typeFormats.put(Long.class, "%15d"); typeFormats.put(BigInteger.class, "%15d"); typeFormats.put(Double.class, "%15f"); typeFormats.put(Float.class, "%15f"); typeFormats.put(BigDecimal.class, "%15f"); typeFormats.put(String.class, "%-50s"); } private List columnNames; private List data; private String format; /** * Used when deserializing a {@linkplain GridDataCommand} instance.
* The instance is then initialized by calling the {@linkplain * GridDataCommand#read(java.io.ObjectInput) } method */ public GridDataCommand() { this(null, null); } /** * Creates a new instance of {@linkplain GridDataCommand} with implicit format * * @param name The aggregation name * @param data The aggregation data */ public GridDataCommand(String name, List data) { this(name, null, data, null); } /** * Creates a new instance of {@linkplain GridDataCommand} with explicit format * * @param name The aggregation name * @param data The aggregation data * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, * java.lang.Object[]) } behaviour with the addition of the ability to address the key title * as a 0-indexed item * @see String#format(java.lang.String, java.lang.Object[]) */ public GridDataCommand(String name, List data, String format) { this(name, null, data, format); } public GridDataCommand(String name, List columnNames, List data) { this(name, columnNames, data, null); } public GridDataCommand(String name, List columnNames, List data, String format) { super(GRID_DATA, name, false); this.columnNames = columnNames != null ? new ArrayList<>(columnNames) : null; this.data = data; this.format = format; } public List getData() { return data; } public List getColumnNames() { return columnNames != null ? new ArrayList<>(columnNames) : null; } /** * Calculates the necessary field width of each column * * @param objects a list of objects * @return a map containing as key the column number and as value the width of the longest value */ private Map getColumnWidth(List objects) { Map columnWidth = new LinkedHashMap<>(); for (Object[] obj : objects) { if (obj == null) { continue; } for (int column = 0; column < obj.length; ++column) { int length = obj[column].toString().length(); Integer width = 0; if (columnWidth.containsKey(column)) { width = columnWidth.get(column); } if (length > width) { columnWidth.put(column, length); } } } return columnWidth; } @Override public void print(PrintWriter out) { if (data != null) { if (name != null && !name.isEmpty()) { out.println(name); } Map columnWidth = getColumnWidth(data); for (Object[] dataRow : data) { if (dataRow == null) { continue; } // Pretty-print multi-line text Object[] printRow = dataRow.clone(); for (int i = 0; i < printRow.length; i++) { if (printRow[i] == null) { printRow[i] = ""; } if (printRow[i] instanceof String) { String value = (String) printRow[i]; if (value.contains("\n")) { printRow[i] = reformatMultilineValue(value); } } } // Format the text String usedFormat = format; if (usedFormat == null || usedFormat.length() == 0) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < printRow.length; i++) { buffer.append(" "); buffer.append(getFormat(printRow[i], columnWidth, i)); } usedFormat = buffer.toString(); } String line = String.format(usedFormat, printRow); out.println(line); } } out.flush(); } /** * Get the format of an object * * @param object get the format of this object * @param columnWidth a map containing as key the column number and as value the width of the * longest value * @param column get the format of that column number * @return the format of an object */ private String getFormat(Object object, Map columnWidth, Integer column) { if (object == null) { return "%-15s"; } String usedFormat = typeFormats.get(object.getClass()); if (usedFormat == null) { return "%-15s"; } if (columnWidth != null && column != null && columnWidth.containsKey(column)) { usedFormat = usedFormat.replaceFirst("\\d+", String.valueOf(columnWidth.get(column))); } return usedFormat; } /** * Takes a multi-line value, prefixes and appends a blank line, and inserts tab characters at the * start of every line. This is derived from how dtrace displays stack traces, and it makes for * pretty readable output. */ private String reformatMultilineValue(String value) { StringBuilder result = new StringBuilder(); result.append("\n"); for (String line : value.split("\n")) { result.append("\t").append(line); result.append("\n"); } return result.toString(); } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(name != null ? name : ""); out.writeUTF(format != null ? format : ""); if (columnNames != null) { out.writeInt(columnNames.size()); for (String columnName : columnNames) { out.writeUTF(columnName != null ? columnName : ""); } } else { out.writeInt(0); } if (data != null) { out.writeInt(data.size()); for (Object[] row : data) { out.writeInt(row.length); for (Object cell : row) { out.writeObject(cell); } } } else { out.writeInt(0); } } @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); format = in.readUTF(); if (format.length() == 0) format = null; int columnCount = in.readInt(); columnNames = new ArrayList<>(columnCount); for (int i = 0; i < columnCount; i++) { String columnName = in.readUTF(); columnNames.add(columnName.length() == 0 ? null : columnName); } int rowCount = in.readInt(); data = new ArrayList<>(rowCount); for (int i = 0; i < rowCount; i++) { int cellCount = in.readInt(); Object[] row = new Object[cellCount]; for (int j = 0; j < cellCount; j++) { row[j] = in.readObject(); } data.add(row); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/InstrumentCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Map; import org.openjdk.btrace.core.ArgsMap; public class InstrumentCommand extends Command { private byte[] code; private ArgsMap args; public InstrumentCommand(byte[] code, ArgsMap args) { super(INSTRUMENT, true); // Defensive copy of bytecode array to prevent external modification this.code = code != null ? code.clone() : null; // Create new ArgsMap by copying entries to prevent external modification if (args != null) { this.args = new ArgsMap(args.size()); for (Map.Entry entry : args) { this.args.put(entry.getKey(), entry.getValue()); } } else { this.args = null; } } public InstrumentCommand(byte[] code, String[] args) { this(code, new ArgsMap(args)); } public InstrumentCommand(byte[] code, Map args) { this(code, new ArgsMap(args)); } protected InstrumentCommand() { this(null, (Map) null); } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(code.length); out.write(code); out.writeInt(args.size()); for (Map.Entry e : args) { out.writeUTF(e.getKey()); String val = e.getValue(); out.writeUTF(val != null ? val : ""); } } @Override protected void read(ObjectInput in) throws IOException { int len = in.readInt(); code = new byte[len]; in.readFully(code); len = in.readInt(); args = new ArgsMap(len); for (int i = 0; i < len; i++) { String key = in.readUTF(); String val = in.readUTF(); args.put(key, val); } } public byte[] getCode() { return code != null ? code.clone() : null; } public ArgsMap getArguments() { return args; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/JavaSerializationProtocol.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; /** * Wire protocol implementation using Java Object Serialization (V1 protocol). * *

This is the original BTrace wire protocol that uses ObjectInputStream/ObjectOutputStream for * command serialization. It provides broad compatibility but has performance limitations compared * to the binary protocol (V2). * *

Characteristics: * *

    *
  • Uses standard Java serialization *
  • Compatible with all BTrace versions *
  • No magic byte prefix *
  • Larger payload sizes *
  • Slower serialization/deserialization *
  • Synchronized write operations *
* *

This implementation wraps the legacy {@link WireIO} class to provide the {@link WireProtocol} * interface. */ public class JavaSerializationProtocol implements WireProtocol { private final ObjectInputStream ois; private final ObjectOutputStream oos; private volatile boolean closed = false; /** * Creates a new JavaSerializationProtocol instance. * * @param inputStream the input stream to read commands from * @param outputStream the output stream to write commands to * @throws IOException if stream initialization fails */ public JavaSerializationProtocol(InputStream inputStream, OutputStream outputStream) throws IOException { // ObjectOutputStream must be created BEFORE ObjectInputStream // to avoid deadlock with stream headers. If ObjectInputStream creation fails, // ensure the already-created ObjectOutputStream is closed to avoid resource leak. ObjectOutputStream tempOos = null; try { tempOos = new ObjectOutputStream(outputStream); tempOos.flush(); // Write stream header immediately this.oos = tempOos; this.ois = new ObjectInputStream(inputStream); } catch (IOException e) { if (tempOos != null) { try { tempOos.close(); } catch (IOException closeEx) { e.addSuppressed(closeEx); } } throw e; } } /** * Creates a JavaSerializationProtocol with existing ObjectStreams. * *

This constructor is provided for backward compatibility with code that already has * ObjectInputStream/ObjectOutputStream instances. * * @param ois the ObjectInputStream to read from * @param oos the ObjectOutputStream to write to */ public JavaSerializationProtocol(ObjectInputStream ois, ObjectOutputStream oos) { this.ois = ois; this.oos = oos; } @Override public Command read() throws IOException, ClassNotFoundException { if (closed) { throw new IOException("Protocol is closed"); } return WireIO.read(ois); } @Override public void write(Command command) throws IOException { if (closed) { throw new IOException("Protocol is closed"); } oos.reset(); WireIO.write(oos, command); } @Override public void flush() throws IOException { if (!closed) { oos.flush(); } } @Override public ProtocolVersion getVersion() { return ProtocolVersion.V1; } @Override public void close() throws IOException { if (closed) { return; } closed = true; IOException exception = null; // Close output stream first to flush any pending data try { oos.close(); } catch (IOException e) { exception = e; } // Then close input stream try { ois.close(); } catch (IOException e) { if (exception == null) { exception = e; } else { exception.addSuppressed(e); } } if (exception != null) { throw exception; } } /** * Returns the underlying ObjectInputStream. * *

This method is provided for backward compatibility with code that needs direct access to the * ObjectInputStream. * * @return the ObjectInputStream */ public ObjectInputStream getObjectInputStream() { return ois; } /** * Returns the underlying ObjectOutputStream. * *

This method is provided for backward compatibility with code that needs direct access to the * ObjectOutputStream. * * @return the ObjectOutputStream */ public ObjectOutputStream getObjectOutputStream() { return oos; } /** * Resets the ObjectOutputStream state. * *

This method is useful for preventing memory leaks in long-running connections by clearing * the stream's object cache. It should be called periodically between writes. * * @throws IOException if an I/O error occurs */ public void reset() throws IOException { if (!closed) { oos.reset(); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ListFailedExtensionsCommand.java ================================================ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; /** * Command to list extensions that failed to load during BTrace agent initialization. * * @since WireIO v.1 */ public class ListFailedExtensionsCommand extends Command implements PrintableCommand { // List of "extensionClassName: errorMessage" entries private final List failedExtensions = new CopyOnWriteArrayList<>(); public ListFailedExtensionsCommand() { super(Command.LIST_FAILED_EXTENSIONS, true); } public void setFailedExtensions(Map failures) { this.failedExtensions.clear(); if (failures != null && !failures.isEmpty()) { for (Map.Entry entry : failures.entrySet()) { failedExtensions.add(entry.getKey() + ": " + entry.getValue()); } } } public void setFailedExtensionsList(List failures) { this.failedExtensions.clear(); if (failures != null && !failures.isEmpty()) { this.failedExtensions.addAll(failures); } } public List getFailedExtensions() { return new ArrayList<>(failedExtensions); } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(failedExtensions.size()); for (String failure : failedExtensions) { out.writeUTF(failure); } } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { int count = in.readInt(); for (int i = 0; i < count; i++) { failedExtensions.add(in.readUTF()); } } @Override public void print(PrintWriter out) { if (failedExtensions.isEmpty()) { out.println("No extension failures detected."); } else { out.println("Failed Extensions:"); int cntr = 1; for (String failure : failedExtensions) { out.println(" " + cntr++ + ". " + failure); } } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ListProbesCommand.java ================================================ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; import java.util.Collection; import java.util.List; import java.util.ArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** * @since WireIO v.1 */ public class ListProbesCommand extends Command implements PrintableCommand { // CopyOnWriteArrayList ensures safe iteration during concurrent updates without CME private final List probes = new CopyOnWriteArrayList<>(); public ListProbesCommand() { super(Command.LIST_PROBES, true); } public void setProbes(Collection probes) { this.probes.clear(); if (probes != null && !probes.isEmpty()) { this.probes.addAll(probes); } } public List getProbes() { return new ArrayList<>(probes); } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(probes.size()); for (String probe : probes) { out.writeUTF(probe); } } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { int numProbes = in.readInt(); for (int i = 0; i < numProbes; i++) { probes.add(in.readUTF()); } } @Override public void print(PrintWriter out) { int cntr = 1; for (String probe : probes) { out.println(cntr++ + ": " + probe); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/MessageCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; public class MessageCommand extends DataCommand { private static final ThreadLocal DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss:SSS")); private long time; private String msg; public MessageCommand(long time, String msg) { this(time, msg, false); } public MessageCommand(long time, String msg, boolean urgent) { super(MESSAGE, null, urgent); this.time = time; this.msg = msg; } public MessageCommand(String msg) { this(msg, false); } public MessageCommand(String msg, boolean urgent) { this(0L, msg, urgent); } protected MessageCommand() { this(0L, null); } @Override protected void write(ObjectOutput out) throws IOException { out.writeBoolean(isUrgent()); out.writeLong(time); byte[] bytes = msg != null ? msg.getBytes(StandardCharsets.UTF_8) : new byte[0]; out.writeInt(bytes.length); if (bytes.length > 0) { out.write(bytes); } } @Override protected void read(ObjectInput in) throws ClassNotFoundException, IOException { if (in.readBoolean()) { setUrgent(); } time = in.readLong(); int len = in.readInt(); byte[] bytes = new byte[len]; int ptr = 0; while (ptr < len) { int bytesRead = in.read(bytes, ptr, len - ptr); if (bytesRead == -1) { throw new IOException("Unexpected end of stream"); } ptr += bytesRead; } msg = new String(bytes, StandardCharsets.UTF_8); } public long getTime() { return time; } public String getMessage() { return msg; } @Override public void print(PrintWriter out) { if (time != 0L) { out.print(DATE_FORMAT.get().format(new Date(time))); out.print(" : "); } if (msg != null) { out.println(msg); } if (isUrgent()) { out.flush(); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberDataCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; /** * A simple data command that has one number value. * * @author A. Sundararajan */ public class NumberDataCommand extends DataCommand { private Number value; public NumberDataCommand() { this(null, 0); } public NumberDataCommand(String name, Number value) { super(NUMBER, name, false); this.value = value; } @Override public void print(PrintWriter out) { if (name != null && !name.isEmpty()) { out.print(name); out.print(" = "); } out.println(value); } public Number getValue() { return value; } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(name != null ? name : ""); out.writeObject(value); } @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); Object obj = in.readObject(); if (obj != null && !(obj instanceof Number)) { throw new IOException( "Invalid data type: expected Number, got " + obj.getClass().getName()); } value = (Number) obj; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberMapDataCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * A data command that hold data of type Map<String, Number>. * * @author A. Sundararajan */ public class NumberMapDataCommand extends DataCommand { private Map data; public NumberMapDataCommand() { this(null, null); } public NumberMapDataCommand(String name, Map data) { super(NUMBER_MAP, name, false); this.data = (data != null) ? new HashMap(data) : null; } public Map getData() { return data; } @Override public void print(PrintWriter out) { if (name != null && !name.isEmpty()) { out.println(name); } if (data != null) { for (Map.Entry e : data.entrySet()) { out.print(e.getKey()); out.print(" = "); out.println(e.getValue()); } } } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(name != null ? name : ""); if (data != null) { out.writeInt(data.size()); for (String key : data.keySet()) { out.writeUTF(key); out.writeObject(data.get(key)); } } else { out.writeInt(0); } } @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); Map map = new HashMap<>(); int sz = in.readInt(); for (int i = 0; i < sz; i++) { map.put(in.readUTF(), (Number) in.readObject()); } data = map; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/PrintableCommand.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.PrintWriter; /** * Marks any command which is able to print its internal info into the provided {@linkplain * PrintWriter} * * @author Jaroslav Bachorik */ public interface PrintableCommand { /** * Print the command internal info * * @param out the associated {@linkplain PrintWriter} */ void print(PrintWriter out); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ProtocolConfig.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; /** * Configuration for BTrace wire protocol. * *

This class provides centralized configuration for protocol version selection and negotiation * behavior. Configuration can be set via system properties or programmatically. * *

System properties: * *

    *
  • btrace.comm.protocol - Sets the protocol version (1 or 2, or "v1"/"v2"). Default: * v2 *
  • btrace.comm.autoNegotiate - Enable automatic protocol negotiation. Default: true *
  • btrace.comm.forceVersion - Force a specific version without negotiation. Default: * false *
* *

Usage example: * *

 * // Use default configuration (V2 with auto-negotiation)
 * ProtocolConfig config = ProtocolConfig.getDefault();
 *
 * // Force V1 protocol
 * ProtocolConfig config = ProtocolConfig.builder()
 *     .version(ProtocolVersion.V1)
 *     .autoNegotiate(false)
 *     .build();
 *
 * // Use system properties
 * System.setProperty("btrace.comm.protocol", "v1");
 * ProtocolConfig config = ProtocolConfig.fromSystemProperties();
 * 
*/ public class ProtocolConfig { private static final String PROP_PROTOCOL = "btrace.comm.protocol"; private static final String PROP_AUTO_NEGOTIATE = "btrace.comm.autoNegotiate"; private static final String PROP_FORCE_VERSION = "btrace.comm.forceVersion"; private final ProtocolVersion version; private final boolean autoNegotiate; private final boolean forceVersion; private ProtocolConfig(ProtocolVersion version, boolean autoNegotiate, boolean forceVersion) { this.version = version; this.autoNegotiate = autoNegotiate; this.forceVersion = forceVersion; if (forceVersion && autoNegotiate) { throw new IllegalArgumentException( "Cannot force version and enable auto-negotiation at the same time"); } } /** * Returns the configured protocol version. * * @return the protocol version to use */ public ProtocolVersion getVersion() { return version; } /** * Returns whether automatic protocol negotiation is enabled. * * @return true if auto-negotiation is enabled, false otherwise */ public boolean isAutoNegotiate() { return autoNegotiate; } /** * Returns whether to force the configured version without negotiation. * * @return true if version should be forced, false otherwise */ public boolean isForceVersion() { return forceVersion; } /** * Returns the default configuration. * *

Default: V2 protocol with auto-negotiation enabled. * * @return the default ProtocolConfig */ public static ProtocolConfig getDefault() { return builder().build(); } /** * Creates a configuration from system properties. * * @return a ProtocolConfig based on system properties, or default if not set */ public static ProtocolConfig fromSystemProperties() { Builder builder = builder(); String protocolProp = System.getProperty(PROP_PROTOCOL); if (protocolProp != null) { builder.version(parseProtocolVersion(protocolProp)); } String autoNegotiateProp = System.getProperty(PROP_AUTO_NEGOTIATE); if (autoNegotiateProp != null) { builder.autoNegotiate(Boolean.parseBoolean(autoNegotiateProp)); } String forceVersionProp = System.getProperty(PROP_FORCE_VERSION); if (forceVersionProp != null) { builder.forceVersion(Boolean.parseBoolean(forceVersionProp)); } return builder.build(); } /** * Parses a protocol version string. * * @param value the version string ("1", "2", "v1", "v2", "V1", "V2") * @return the corresponding ProtocolVersion * @throws IllegalArgumentException if the version string is invalid */ private static ProtocolVersion parseProtocolVersion(String value) { if (value == null || value.trim().isEmpty()) { return ProtocolVersion.getDefault(); } String normalized = value.trim().toLowerCase(); switch (normalized) { case "1": case "v1": return ProtocolVersion.V1; case "2": case "v2": return ProtocolVersion.V2; default: throw new IllegalArgumentException("Invalid protocol version: " + value); } } /** * Creates a new builder for ProtocolConfig. * * @return a new Builder instance */ public static Builder builder() { return new Builder(); } @Override public String toString() { return "ProtocolConfig{" + "version=" + version + ", autoNegotiate=" + autoNegotiate + ", forceVersion=" + forceVersion + '}'; } /** Builder for ProtocolConfig. */ public static class Builder { private ProtocolVersion version = ProtocolVersion.getDefault(); private boolean autoNegotiate = true; private boolean forceVersion = false; private Builder() {} /** * Sets the protocol version. * * @param version the protocol version to use * @return this builder */ public Builder version(ProtocolVersion version) { this.version = version; return this; } /** * Sets whether to enable automatic protocol negotiation. * * @param autoNegotiate true to enable auto-negotiation, false otherwise * @return this builder */ public Builder autoNegotiate(boolean autoNegotiate) { this.autoNegotiate = autoNegotiate; return this; } /** * Sets whether to force the configured version without negotiation. * * @param forceVersion true to force version, false otherwise * @return this builder */ public Builder forceVersion(boolean forceVersion) { this.forceVersion = forceVersion; return this; } /** * Builds the ProtocolConfig. * * @return a new ProtocolConfig instance * @throws IllegalArgumentException if configuration is invalid */ public ProtocolConfig build() { return new ProtocolConfig(version, autoNegotiate, forceVersion); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ProtocolNegotiator.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.util.Arrays; /** * Handles protocol version negotiation for BTrace command communication. * *

Protocol negotiation happens ONCE PER CONNECTION by examining the first few bytes from the * stream. The negotiator detects which protocol version is being used based on magic byte prefixes: * *

    *
  • V2: Starts with 0x42 0x54 0x52 0x32 ("BTR2") *
  • V1: No magic bytes, uses Java serialization format *
* *

Usage example: * *

 * InputStream is = socket.getInputStream();
 * ProtocolNegotiator negotiator = new ProtocolNegotiator();
 * ProtocolVersion version = negotiator.negotiate(is);
 *
 * if (version == ProtocolVersion.V2) {
 *   // Use binary protocol
 *   BinaryCommand cmd = BinaryWireIO.read(is);
 * } else {
 *   // Use Java serialization
 *   Command cmd = WireIO.read(new ObjectInputStream(is));
 * }
 * 
*/ public class ProtocolNegotiator { /** Maximum size of magic byte prefix to detect */ private static final int MAX_MAGIC_BYTES = 4; // Length of "BTR2" private static final String PROP_NEGOTIATION_TIMEOUT = "btrace.protocol.negotiation.timeout"; private static final int DEFAULT_NEGOTIATION_TIMEOUT_MS = 5000; private final ProtocolVersion preferredVersion; /** Creates a negotiator with the default preferred version (V2). */ public ProtocolNegotiator() { this(ProtocolVersion.getDefault()); } /** * Creates a negotiator with a specific preferred version. * * @param preferredVersion the protocol version to prefer when initiating connections */ public ProtocolNegotiator(ProtocolVersion preferredVersion) { this.preferredVersion = preferredVersion; } /** * Negotiates the protocol version by detecting magic bytes from the input stream. * *

This method reads up to {@link #MAX_MAGIC_BYTES} bytes from the stream to detect the * protocol version. The bytes are pushed back onto the stream (if using PushbackInputStream) or * the stream is marked and reset. * *

IMPORTANT: This method must be called ONCE per connection, before any other read * operations. The stream must support either: * *

    *
  • PushbackInputStream with at least {@link #MAX_MAGIC_BYTES} bytes pushback buffer *
  • mark/reset with at least {@link #MAX_MAGIC_BYTES} read limit *
* * @param is the input stream to negotiate protocol on * @return the detected ProtocolVersion (V2 if magic bytes match, V1 otherwise) * @throws IOException if an I/O error occurs * @throws IllegalArgumentException if the stream doesn't support pushback or mark/reset */ public ProtocolVersion negotiate(InputStream is) throws IOException { if (is instanceof PushbackInputStream) { return negotiateWithPushback((PushbackInputStream) is); } else if (is.markSupported()) { return negotiateWithMark(is); } else { throw new IllegalArgumentException( "InputStream must be either PushbackInputStream or support mark/reset"); } } /** * Negotiates protocol version using PushbackInputStream. * * @param pis the pushback input stream * @return the detected protocol version * @throws IOException if an I/O error occurs */ private ProtocolVersion negotiateWithPushback(PushbackInputStream pis) throws IOException { byte[] prefix = new byte[MAX_MAGIC_BYTES]; int bytesRead = readFully(pis, prefix); if (bytesRead < MAX_MAGIC_BYTES) { throw new IOException("Connection closed during protocol negotiation"); } // Detect protocol version ProtocolVersion detected = ProtocolVersion.detectFromPrefix(prefix); // Push back the bytes we read if (detected == ProtocolVersion.V2) { // V2 magic bytes are consumed by the protocol, don't push back // The V2 protocol expects to read these as part of its header } else { // V1 has no magic bytes, push everything back pis.unread(prefix, 0, bytesRead); } return detected; } /** * Negotiates protocol version using mark/reset. * * @param is the input stream supporting mark/reset * @return the detected protocol version * @throws IOException if an I/O error occurs */ private ProtocolVersion negotiateWithMark(InputStream is) throws IOException { is.mark(MAX_MAGIC_BYTES); byte[] prefix = new byte[MAX_MAGIC_BYTES]; int bytesRead = readFully(is, prefix); if (bytesRead < MAX_MAGIC_BYTES) { throw new IOException("Connection closed during protocol negotiation"); } // Detect protocol version ProtocolVersion detected = ProtocolVersion.detectFromPrefix(prefix); // Reset stream to beginning if (detected == ProtocolVersion.V2) { // V2 magic bytes are part of the protocol, don't reset // The stream is now positioned after the magic bytes } else { // V1 has no magic bytes, reset to beginning is.reset(); } return detected; } /** * Negotiates protocol version on the agent side. * *

Reads the magic prefix sent by the client. If V2 is detected, responds with the same magic * bytes. If V1 is detected, pushes the bytes back so Java serialization can read the header. * * @param input the input stream (must support pushback) * @param output the output stream * @return the negotiated ProtocolVersion * @throws IOException if negotiation fails */ public ProtocolVersion negotiateAgent(PushbackInputStream input, OutputStream output) throws IOException { ProtocolVersion detected = negotiateWithPushback(input); if (detected == ProtocolVersion.V2) { output.write(ProtocolVersion.V2.getMagicBytes()); output.flush(); } return detected; } /** * Negotiates protocol version on the client side. * *

If the preferred version is V2, sends the V2 magic bytes and waits for the agent response. * Throws if the response does not match the V2 magic bytes. * * @param input the input stream * @param output the output stream * @param preferred the preferred protocol version * @return the negotiated ProtocolVersion * @throws IOException if negotiation fails */ public ProtocolVersion negotiateClient( InputStream input, OutputStream output, ProtocolVersion preferred) throws IOException { if (preferred != ProtocolVersion.V2) { return ProtocolVersion.V1; } byte[] magic = ProtocolVersion.V2.getMagicBytes(); output.write(magic); output.flush(); byte[] response = new byte[magic.length]; int read = readFully(input, response); if (read < magic.length) { throw new IOException("Connection closed during protocol negotiation"); } if (!Arrays.equals(magic, response)) { throw new IOException("Protocol negotiation failed: unexpected response"); } return ProtocolVersion.V2; } /** * Returns the preferred protocol version for initiating connections. * * @return the preferred ProtocolVersion */ public ProtocolVersion getPreferredVersion() { return preferredVersion; } /** * Creates a PushbackInputStream suitable for protocol negotiation. * *

The returned stream has a pushback buffer of {@link #MAX_MAGIC_BYTES} bytes, which is * sufficient for detecting all protocol versions. * * @param is the underlying input stream * @return a PushbackInputStream with appropriate buffer size */ public static PushbackInputStream createNegotiationStream(InputStream is) { if (is instanceof PushbackInputStream) { return (PushbackInputStream) is; } return new PushbackInputStream(is, MAX_MAGIC_BYTES); } public static int getNegotiationTimeoutMs() { String value = System.getProperty(PROP_NEGOTIATION_TIMEOUT); if (value == null || value.trim().isEmpty()) { return DEFAULT_NEGOTIATION_TIMEOUT_MS; } try { return Integer.parseInt(value.trim()); } catch (NumberFormatException e) { return DEFAULT_NEGOTIATION_TIMEOUT_MS; } } private static int readFully(InputStream in, byte[] buffer) throws IOException { int offset = 0; while (offset < buffer.length) { int read = in.read(buffer, offset, buffer.length - offset); if (read < 0) { break; } offset += read; } return offset; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ProtocolVersion.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; /** * Represents the wire protocol version for BTrace command communication. * *

BTrace supports multiple wire protocol versions to allow for performance improvements while * maintaining backward compatibility. */ public enum ProtocolVersion { /** * Version 1: Java ObjectInputStream/ObjectOutputStream serialization. * *

This is the original protocol using standard Java serialization. Compatible with all BTrace * versions. * *

Characteristics: * *

    *
  • Uses ObjectOutputStream for serialization *
  • No magic byte prefix *
  • Larger payload sizes *
  • Slower serialization/deserialization *
*/ V1(1, new byte[] {}), /** * Version 2: Custom binary protocol with compression. * *

High-performance binary protocol with automatic compression for large payloads. * *

Characteristics: * *

    *
  • Magic byte prefix: 0x42 0x54 0x52 0x32 ("BTR2") *
  • 3-6x faster serialization *
  • 2-5x smaller payloads (10-100x with compression) *
  • Automatic deflate compression for payloads > 1KB *
  • Zero-copy optimizations where possible *
*/ V2(2, new byte[] {0x42, 0x54, 0x52, 0x32}); // "BTR2" private final int version; private final byte[] magicBytes; ProtocolVersion(int version, byte[] magicBytes) { this.version = version; this.magicBytes = magicBytes; } /** * Returns the protocol version number. * * @return the version number (1 or 2) */ public int getVersion() { return version; } /** * Returns the magic byte prefix for this protocol version. * *

V1 has no magic bytes (empty array). V2 uses "BTR2" (0x42 0x54 0x52 0x32). * * @return the magic byte prefix, or empty array if none */ public byte[] getMagicBytes() { return magicBytes.clone(); } /** * Returns whether this protocol version uses a magic byte prefix. * * @return true if this version has magic bytes, false otherwise */ public boolean hasMagicBytes() { return magicBytes.length > 0; } /** * Looks up a protocol version by its version number. * * @param version the version number (1 or 2) * @return the corresponding ProtocolVersion * @throws IllegalArgumentException if the version number is not supported */ public static ProtocolVersion fromVersion(int version) { for (ProtocolVersion pv : values()) { if (pv.version == version) { return pv; } } throw new IllegalArgumentException("Unsupported protocol version: " + version); } /** * Detects the protocol version from a magic byte prefix. * *

This method checks if the provided bytes match any known magic byte prefix. If no match is * found, it assumes V1 (which has no magic bytes). * * @param prefix the first few bytes from the stream * @return the detected ProtocolVersion (V2 if magic bytes match, V1 otherwise) */ public static ProtocolVersion detectFromPrefix(byte[] prefix) { if (prefix == null || prefix.length == 0) { return V1; } // Check V2 magic bytes if (prefix.length >= V2.magicBytes.length) { boolean matches = true; for (int i = 0; i < V2.magicBytes.length; i++) { if (prefix[i] != V2.magicBytes[i]) { matches = false; break; } } if (matches) { return V2; } } // Default to V1 if no magic bytes match return V1; } /** * Returns the default protocol version. * *

Currently defaults to V2 for new connections, but can be overridden via configuration. * * @return the default ProtocolVersion (V2) */ public static ProtocolVersion getDefault() { return V2; } @Override public String toString() { return "ProtocolVersion{version=" + version + ", hasMagicBytes=" + hasMagicBytes() + "}"; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/ReconnectCommand.java ================================================ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; /** * @since WireIO v.1 */ public class ReconnectCommand extends Command { public static final int STATUS_FLAG = 8; private String probeId; public ReconnectCommand() { super(Command.RECONNECT); } public ReconnectCommand(String probeId) { super(Command.RECONNECT); this.probeId = probeId; } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(probeId); } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { probeId = in.readUTF(); } public String getProbeId() { return probeId; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/RenameCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class RenameCommand extends Command { private String newName; public RenameCommand(String newName) { super(RENAME, true); this.newName = newName; } protected RenameCommand() { this(null); } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(newName); } @Override protected void read(ObjectInput in) throws IOException { newName = in.readUTF(); } public String getNewName() { return newName; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformClassNotification.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; /** * This command is sent out as a notification that a class is going to be transformed * * @author Jaroslav Bachorik jaroslav.bachorik@sun.com */ public class RetransformClassNotification extends Command implements PrintableCommand { private String className; public RetransformClassNotification(String className) { super(RETRANSFORM_CLASS, true); this.className = className; } public RetransformClassNotification() { super(RETRANSFORM_CLASS); } @Override protected void write(ObjectOutput out) throws IOException { out.writeObject(className); } @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { className = (String) in.readObject(); } public String getClassName() { return className; } @Override public void print(PrintWriter out) { out.append("Going to retransform class ").append(className).append('\n'); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformationStartNotification.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.lang.instrument.Instrumentation; /** * This command is sent out when the BTrace engine calls {@linkplain * Instrumentation#retransformClasses(Class[])} method. It is followed by {@linkplain StatusCommand} * command when the retransformation ends. * * @author Jaroslav Bachorik jaroslav.bachorik@sun.com */ public class RetransformationStartNotification extends Command { private int numClasses; public RetransformationStartNotification() { super(RETRANSFORMATION_START); } public RetransformationStartNotification(int numClasses) { super(RETRANSFORMATION_START, true); this.numClasses = numClasses; } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(numClasses); } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { numClasses = in.readInt(); } public int getNumClasses() { return numClasses; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/SetSettingsCommand.java ================================================ /* * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.HashMap; import java.util.Map; /** * This command is used to remotely set custom settings (trusted mode, debug, etc.) * * @author Jaroslav Bachorik */ public class SetSettingsCommand extends Command { private final Map params; public SetSettingsCommand(Map params) { super(SET_PARAMS, true); this.params = params != null ? new HashMap<>(params) : new HashMap<>(); } protected SetSettingsCommand() { this(null); } public Map getParams() { return params; } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(params.size()); for (Map.Entry e : params.entrySet()) { out.writeUTF(e.getKey()); out.writeObject(e.getValue()); } } @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size < 0 || size > 10000) { // Reasonable limit to prevent memory exhaustion throw new IOException("Invalid params size: " + size); } for (int i = 0; i < size; i++) { String k = in.readUTF(); Object v = in.readObject(); // Validate that value is a reasonable settings type if (v != null && !(v instanceof String) && !(v instanceof Number) && !(v instanceof Boolean)) { throw new IOException( "Invalid settings value type: expected String/Number/Boolean, got " + v.getClass().getName()); } params.put(k, v); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/StatusCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class StatusCommand extends Command { public static final int STATUS_FLAG = 1; // custom flag private int flag; public StatusCommand(int flag) { super(STATUS, true); this.flag = flag; } public StatusCommand() { this((byte) 0); } @Override protected void write(ObjectOutput out) throws IOException { out.writeInt(flag); } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { flag = in.readInt(); } public int getFlag() { return Math.abs(flag); } public boolean isSuccess() { return flag >= 0; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/StringMapDataCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.PrintWriter; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * A data command that hold data of type Map<String, String>. * * @author A. Sundararajan */ public class StringMapDataCommand extends DataCommand { private Map data; public StringMapDataCommand() { this(null, null); } public StringMapDataCommand(String name, Map data) { super(STRING_MAP, name, false); if (data != null) { this.data = new HashMap<>(data); } else { this.data = Collections.emptyMap(); } } public Map getData() { return data; } @Override public void print(PrintWriter out) { if (name != null && !name.isEmpty()) { out.println(name); } for (Map.Entry e : data.entrySet()) { out.print(e.getKey()); out.print(" = "); out.println(e.getValue()); } } @Override protected void write(ObjectOutput out) throws IOException { out.writeUTF(name != null ? name : ""); out.writeInt(data.size()); for (String key : data.keySet()) { out.writeUTF(key); out.writeUTF(data.get(key)); } } @SuppressWarnings("RedundantThrows") @Override protected void read(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); data = new HashMap<>(); int sz = in.readInt(); for (int i = 0; i < sz; i++) { data.put(in.readUTF(), in.readUTF()); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/WireIO.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class WireIO { public static final int VERSION = 1; private WireIO() {} public static Command read(ObjectInput in) throws IOException { byte type = in.readByte(); Command cmd; switch (type) { case Command.ERROR: cmd = new ErrorCommand(); break; case Command.EVENT: cmd = new EventCommand(); break; case Command.EXIT: cmd = new ExitCommand(); break; case Command.INSTRUMENT: cmd = new InstrumentCommand(); break; case Command.MESSAGE: cmd = new MessageCommand(); break; case Command.RENAME: cmd = new RenameCommand(); break; case Command.STATUS: cmd = new StatusCommand(); break; case Command.NUMBER_MAP: cmd = new NumberMapDataCommand(); break; case Command.STRING_MAP: cmd = new StringMapDataCommand(); break; case Command.NUMBER: cmd = new NumberDataCommand(); break; case Command.GRID_DATA: cmd = new GridDataCommand(); break; case Command.RETRANSFORMATION_START: cmd = new RetransformationStartNotification(); break; case Command.RETRANSFORM_CLASS: cmd = new RetransformClassNotification(); break; case Command.SET_PARAMS: cmd = new SetSettingsCommand(); break; case Command.LIST_PROBES: cmd = new ListProbesCommand(); break; case Command.DISCONNECT: cmd = new DisconnectCommand(); break; case Command.RECONNECT: cmd = new ReconnectCommand(); break; default: throw new RuntimeException("invalid command: " + type); } try { cmd.read(in); } catch (ClassNotFoundException cnfe) { throw new IOException(cnfe); } return cmd; } @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public static void write(ObjectOutput out, Command cmd) throws IOException { synchronized (out) { out.writeByte(cmd.getType()); cmd.write(out); if (cmd.isUrgent()) { out.flush(); } } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/WireProtocol.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Abstract wire protocol for BTrace command communication. * *

This interface provides a pluggable abstraction for different wire protocol implementations, * allowing BTrace to support multiple serialization formats while maintaining a consistent API. * *

Implementations: * *

    *
  • {@link JavaSerializationProtocol} - V1 protocol using ObjectInputStream/ObjectOutputStream *
  • {@link BinaryWireProtocol} - V2 protocol using custom binary format *
* *

Usage example: * *

 * // Create protocol based on negotiation
 * InputStream is = socket.getInputStream();
 * OutputStream os = socket.getOutputStream();
 *
 * ProtocolNegotiator negotiator = new ProtocolNegotiator();
 * ProtocolVersion version = negotiator.negotiate(createNegotiationStream(is));
 *
 * WireProtocol protocol = WireProtocol.create(version, is, os);
 *
 * // Read commands
 * Command cmd = protocol.read();
 *
 * // Write commands
 * protocol.write(new MessageCommand("hello"));
 *
 * // Close when done
 * protocol.close();
 * 
*/ public interface WireProtocol extends Closeable { /** * Reads a command from the input stream. * *

This method blocks until a complete command is available or an error occurs. * * @return the command read from the stream * @throws IOException if an I/O error occurs or the stream is closed * @throws ClassNotFoundException if the command type cannot be deserialized (V1 only) */ Command read() throws IOException, ClassNotFoundException; /** * Writes a command to the output stream. * *

If the command is marked as urgent, the stream will be flushed immediately. Otherwise, * buffering behavior depends on the underlying protocol implementation. * * @param command the command to write * @throws IOException if an I/O error occurs or the stream is closed */ void write(Command command) throws IOException; /** * Flushes any buffered data to the output stream. * * @throws IOException if an I/O error occurs */ void flush() throws IOException; /** * Returns the protocol version in use. * * @return the ProtocolVersion */ ProtocolVersion getVersion(); /** * Closes the protocol and releases any associated resources. * *

After calling this method, no further read or write operations should be performed. * * @throws IOException if an I/O error occurs */ @Override void close() throws IOException; /** * Creates a WireProtocol instance for the specified version. * *

The streams should already be positioned correctly after protocol negotiation. * * @param version the protocol version to use * @param inputStream the input stream to read commands from * @param outputStream the output stream to write commands to * @return a WireProtocol instance for the specified version * @throws IOException if protocol initialization fails */ static WireProtocol create( ProtocolVersion version, InputStream inputStream, OutputStream outputStream) throws IOException { switch (version) { case V1: return new JavaSerializationProtocol(inputStream, outputStream); case V2: return new BinaryWireProtocol(inputStream, outputStream); default: throw new IllegalArgumentException("Unsupported protocol version: " + version); } } /** * Creates a WireProtocol instance with automatic protocol negotiation. * *

This method performs protocol negotiation on the input stream and creates the appropriate * WireProtocol implementation. The inputStream must support either PushbackInputStream or * mark/reset. * * @param inputStream the input stream (will be wrapped in PushbackInputStream if needed) * @param outputStream the output stream * @return a WireProtocol instance for the negotiated version * @throws IOException if negotiation or protocol initialization fails */ static WireProtocol createWithNegotiation(InputStream inputStream, OutputStream outputStream) throws IOException { ProtocolNegotiator negotiator = new ProtocolNegotiator(); InputStream negotiationStream = ProtocolNegotiator.createNegotiationStream(inputStream); ProtocolVersion version = negotiator.negotiate(negotiationStream); return create(version, negotiationStream, outputStream); } /** * Creates a WireProtocol instance with configuration-based setup. * *

If auto-negotiation is enabled in the config, performs negotiation. If forceVersion is set, * uses the configured version without negotiation. * * @param config the protocol configuration * @param inputStream the input stream * @param outputStream the output stream * @return a WireProtocol instance based on configuration * @throws IOException if protocol setup fails */ static WireProtocol createWithConfig( ProtocolConfig config, InputStream inputStream, OutputStream outputStream) throws IOException { if (config.isForceVersion()) { // Force specific version without negotiation return create(config.getVersion(), inputStream, outputStream); } else if (config.isAutoNegotiate()) { // Perform negotiation return createWithNegotiation(inputStream, outputStream); } else { // Use configured version without negotiation return create(config.getVersion(), inputStream, outputStream); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryClient.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.CommandListener; /** * A client wrapper that uses the binary protocol for communication. * This provides a high-performance alternative to the standard Java serialization. */ public class BinaryClient { private final InputStream inputStream; private final OutputStream outputStream; private final CommandListener commandListener; private final ReentrantLock readLock = new ReentrantLock(); private final ReentrantLock writeLock = new ReentrantLock(); private volatile boolean closed = false; /** * Create a new binary client with the specified streams and command listener */ public BinaryClient(InputStream inputStream, OutputStream outputStream, CommandListener commandListener) { this.inputStream = inputStream; this.outputStream = outputStream; this.commandListener = commandListener; } /** * Send an event command */ public void sendEvent(String event) throws IOException { send(new BinaryEventCommand(event)); } /** * Send an exit command */ public void sendExit(int exitCode) throws IOException { send(new BinaryExitCommand(exitCode)); } /** * Send an instrument command */ public void sendInstrument(byte[] code, String[] args) throws IOException { send(new BinaryInstrumentCommand(code, args)); } /** * Send an instrument command */ public void sendInstrument(byte[] code, Map args) throws IOException { send(new BinaryInstrumentCommand(code, args)); } /** * Send a message command */ public void sendMessage(String message, boolean urgent) throws IOException { send(new BinaryMessageCommand(message, urgent)); } /** * Send a binary command */ public void send(BinaryCommand cmd) throws IOException { if (closed) { throw new IOException("Client is closed"); } writeLock.lock(); try { BinaryWireIO.write(outputStream, cmd); } finally { writeLock.unlock(); } } /** * Send an original BTrace command (will be converted to binary format) */ public void send(Command cmd) throws IOException { send(CommandAdapter.toBinaryCommand(cmd)); } /** * Read and process commands in a loop */ public void commandLoop() throws IOException { if (closed) { throw new IOException("Client is closed"); } try { while (!closed) { BinaryCommand cmd = readCommand(); Command btraceCmd = CommandAdapter.toBtraceCommand(cmd); commandListener.onCommand(btraceCmd); if (cmd.getType() == BinaryCommand.EXIT) { break; } } } catch (IOException e) { if (!closed) { throw e; } } } /** * Read a single command from the input stream */ public BinaryCommand readCommand() throws IOException { if (closed) { throw new IOException("Client is closed"); } readLock.lock(); try { return BinaryWireIO.read(inputStream); } finally { readLock.unlock(); } } /** * Close the client and all associated resources */ public void close() throws IOException { if (closed) { return; } closed = true; if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; /** * Base class for all commands in the binary protocol. * This replaces the original Command class that relied on Java serialization. */ public abstract class BinaryCommand { // Command types - must match the original Command class for compatibility public static final byte ERROR = 0; public static final byte EVENT = 1; public static final byte EXIT = 2; public static final byte INSTRUMENT = 3; public static final byte MESSAGE = 4; public static final byte RENAME = 5; public static final byte STATUS = 6; public static final byte NUMBER_MAP = 7; public static final byte STRING_MAP = 8; public static final byte NUMBER = 9; public static final byte GRID_DATA = 10; public static final byte RETRANSFORMATION_START = 11; public static final byte RETRANSFORM_CLASS = 12; public static final byte SET_PARAMS = 13; public static final byte LIST_PROBES = 14; public static final byte DISCONNECT = 15; public static final byte RECONNECT = 16; public static final byte LIST_FAILED_EXTENSIONS = 17; public static final byte FIRST_COMMAND = ERROR; public static final byte LAST_COMMAND = LIST_FAILED_EXTENSIONS; // Used for command registration and creation private static final Map> COMMAND_FACTORIES = new HashMap<>(); // Register command factories static { registerCommand(ERROR, BinaryErrorCommand::new); registerCommand(EVENT, BinaryEventCommand::new); registerCommand(EXIT, BinaryExitCommand::new); registerCommand(INSTRUMENT, BinaryInstrumentCommand::new); registerCommand(MESSAGE, BinaryMessageCommand::new); registerCommand(RENAME, BinaryRenameCommand::new); registerCommand(STATUS, BinaryStatusCommand::new); registerCommand(NUMBER_MAP, BinaryNumberMapDataCommand::new); registerCommand(STRING_MAP, BinaryStringMapDataCommand::new); registerCommand(NUMBER, BinaryNumberDataCommand::new); registerCommand(GRID_DATA, BinaryGridDataCommand::new); registerCommand(RETRANSFORMATION_START, BinaryRetransformationStartNotification::new); registerCommand(RETRANSFORM_CLASS, BinaryRetransformClassNotification::new); registerCommand(SET_PARAMS, BinarySetSettingsCommand::new); registerCommand(LIST_PROBES, BinaryListProbesCommand::new); registerCommand(DISCONNECT, BinaryDisconnectCommand::new); registerCommand(RECONNECT, BinaryReconnectCommand::new); registerCommand(LIST_FAILED_EXTENSIONS, BinaryListFailedExtensionsCommand::new); } /** * Register a command factory for a specific command type */ public static void registerCommand(byte type, Supplier factory) { COMMAND_FACTORIES.put(type, factory); } /** * Create a command instance for the given type */ public static BinaryCommand createCommand(byte type) { Supplier factory = COMMAND_FACTORIES.get(type); if (factory == null) { throw new IllegalArgumentException("Unknown command type: " + type); } return factory.get(); } protected byte type; private boolean urgent; protected BinaryCommand(byte type) { this(type, true); } protected BinaryCommand(byte type, boolean urgent) { if (type < FIRST_COMMAND || type > LAST_COMMAND) { throw new IllegalArgumentException("Invalid command type: " + type); } this.type = type; this.urgent = urgent; } /** * Write this command to the output stream */ protected abstract void write(OutputStream out) throws IOException; /** * Read this command from the input stream */ protected abstract void read(InputStream in) throws IOException; /** * Get the type of this command */ public final byte getType() { return type; } /** * Check if this command needs urgent processing */ public final boolean isUrgent() { return urgent; } /** * Set this command as urgent */ final void setUrgent() { urgent = true; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryDataCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Abstract base class for data commands. * These commands are used to send monitoring data from the BTrace agent to the client. */ public abstract class BinaryDataCommand extends BinaryCommand { protected String name; protected BinaryDataCommand(byte type, String name) { super(type, false); this.name = name; } protected BinaryDataCommand(byte type) { this(type, null); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeString(out, name); } @Override protected void read(InputStream in) throws IOException { name = BinaryProtocol.readString(in); } public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryDisconnectCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; /** * Binary implementation of the DisconnectCommand. * This command is used to disconnect a BTrace client without stopping the agent. */ public class BinaryDisconnectCommand extends BinaryStringCommand { static { // Register this command type BinaryCommand.registerCommand(DISCONNECT, BinaryDisconnectCommand::new); } public BinaryDisconnectCommand(String probeId) { super(DISCONNECT, probeId); } public BinaryDisconnectCommand() { this(""); } public String getProbeId() { return getPayload(); } public void setProbeId(String probeId) { setPayload(probeId); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryErrorCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Binary implementation of the ErrorCommand. * This command is used to send error information from the BTrace agent to the client. */ public class BinaryErrorCommand extends BinaryCommand { private String exceptionClass; private String message; private String stackTrace; static { // Register this command type BinaryCommand.registerCommand(ERROR, BinaryErrorCommand::new); } public BinaryErrorCommand(String exceptionClass, String message, String stackTrace) { super(ERROR, true); this.exceptionClass = exceptionClass; this.message = message; this.stackTrace = stackTrace; } public BinaryErrorCommand() { this(null, null, null); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeString(out, exceptionClass); BinaryProtocol.writeString(out, message); BinaryProtocol.writeString(out, stackTrace); } @Override protected void read(InputStream in) throws IOException { exceptionClass = BinaryProtocol.readString(in); message = BinaryProtocol.readString(in); stackTrace = BinaryProtocol.readString(in); } public String getExceptionClass() { return exceptionClass; } public void setExceptionClass(String exceptionClass) { this.exceptionClass = exceptionClass; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getStackTrace() { return stackTrace; } public void setStackTrace(String stackTrace) { this.stackTrace = stackTrace; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryEventCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; /** * Binary implementation of the EventCommand. * This command is used to send events to BTrace agents. */ public class BinaryEventCommand extends BinaryStringCommand { static { // Register this command type BinaryCommand.registerCommand(EVENT, BinaryEventCommand::new); } public BinaryEventCommand(String event) { super(EVENT, event); } public BinaryEventCommand() { this(null); } public String getEvent() { return getPayload(); } public void setEvent(String event) { setPayload(event); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryExitCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Binary implementation of the ExitCommand. * This command is used to signal the BTrace agent to exit. */ public class BinaryExitCommand extends BinaryCommand { private int exitCode; static { // Register this command type BinaryCommand.registerCommand(EXIT, BinaryExitCommand::new); } public BinaryExitCommand(int exitCode) { super(EXIT, true); this.exitCode = exitCode; } public BinaryExitCommand() { this(0); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeInt(out, exitCode); } @Override protected void read(InputStream in) throws IOException { exitCode = BinaryProtocol.readInt(in); } public int getExitCode() { return exitCode; } public void setExitCode(int exitCode) { this.exitCode = exitCode; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryGridDataCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; /** * Binary implementation of the GridDataCommand. * This command is used to send tabular data from the BTrace agent to the client. */ public class BinaryGridDataCommand extends BinaryDataCommand { private List columnNames = new ArrayList<>(); private List data = new ArrayList<>(); private static final ScalarEncoding SCALAR = new ScalarEncoding((byte)0, (byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6); static { // Register this command type BinaryCommand.registerCommand(GRID_DATA, BinaryGridDataCommand::new); } public BinaryGridDataCommand(String name, List columnNames, List data) { super(GRID_DATA, name); if (columnNames != null) { this.columnNames.addAll(columnNames); } if (data != null) { this.data.addAll(data); } } public BinaryGridDataCommand() { this(null, null, null); } @Override protected void write(OutputStream out) throws IOException { // Write the name super.write(out); // Write the column names BinaryProtocol.writeInt(out, columnNames.size()); for (String columnName : columnNames) { BinaryProtocol.writeString(out, columnName); } // Write the data BinaryProtocol.writeInt(out, data.size()); for (Object[] row : data) { // Write the row length int rowLength = row != null ? row.length : 0; BinaryProtocol.writeInt(out, rowLength); // Write each cell if (row != null) { for (Object cell : row) { SCALAR.writeValue(out, cell); } } } } @Override protected void read(InputStream in) throws IOException { // Read the name super.read(in); // Read the column names int columnCount = BinaryProtocol.readInt(in); columnNames.clear(); for (int i = 0; i < columnCount; i++) { columnNames.add(BinaryProtocol.readString(in)); } // Read the data int rowCount = BinaryProtocol.readInt(in); data.clear(); for (int i = 0; i < rowCount; i++) { // Read the row length int rowLength = BinaryProtocol.readInt(in); // Read each cell Object[] row = new Object[rowLength]; for (int j = 0; j < rowLength; j++) { row[j] = SCALAR.readValue(in); } data.add(row); } } public List getColumnNames() { return new ArrayList<>(columnNames); } public void setColumnNames(List columnNames) { this.columnNames.clear(); if (columnNames != null) { this.columnNames.addAll(columnNames); } } public List getData() { List result = new ArrayList<>(data.size()); for (Object[] row : data) { result.add(row.clone()); } return result; } public void setData(List data) { this.data.clear(); if (data != null) { for (Object[] row : data) { this.data.add(row.clone()); } } } public void addRow(Object[] row) { if (row != null) { this.data.add(row.clone()); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryInstrumentCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import org.openjdk.btrace.core.ArgsMap; /** * Binary implementation of the InstrumentCommand. * This command is used to send BTrace code to the target VM for instrumentation. */ public class BinaryInstrumentCommand extends BinaryCommand { private byte[] code; private ArgsMap args; static { // Register this command type BinaryCommand.registerCommand(INSTRUMENT, BinaryInstrumentCommand::new); } public BinaryInstrumentCommand(byte[] code, ArgsMap args) { super(INSTRUMENT, true); this.code = code; this.args = args; } public BinaryInstrumentCommand(byte[] code, String[] args) { this(code, new ArgsMap(args)); } public BinaryInstrumentCommand(byte[] code, Map args) { this(code, new ArgsMap(args)); } public BinaryInstrumentCommand() { this(null, (Map) null); } @Override protected void write(OutputStream out) throws IOException { // Write code bytes BinaryProtocol.writeByteArray(out, code); // Write args count BinaryProtocol.writeInt(out, args.size()); // Write args for (Map.Entry e : args) { BinaryProtocol.writeString(out, e.getKey()); BinaryProtocol.writeString(out, e.getValue() != null ? e.getValue() : ""); } } @Override protected void read(InputStream in) throws IOException { // Read code bytes code = BinaryProtocol.readByteArray(in); // Read args count int argsCount = BinaryProtocol.readInt(in); // Read args args = new ArgsMap(argsCount); for (int i = 0; i < argsCount; i++) { String key = BinaryProtocol.readString(in); String val = BinaryProtocol.readString(in); args.put(key, val); } } public byte[] getCode() { return code; } public ArgsMap getArguments() { return args; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryListFailedExtensionsCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Binary implementation of the ListFailedExtensionsCommand. * This command is used to list extensions that failed to load. */ public class BinaryListFailedExtensionsCommand extends BinaryCommand { private final List failures = new ArrayList<>(); static { BinaryCommand.registerCommand(LIST_FAILED_EXTENSIONS, BinaryListFailedExtensionsCommand::new); } public BinaryListFailedExtensionsCommand() { super(LIST_FAILED_EXTENSIONS, true); } public void setFailures(Collection failures) { this.failures.clear(); if (failures != null) { this.failures.addAll(failures); } } public List getFailures() { return new ArrayList<>(failures); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeInt(out, failures.size()); for (String failure : failures) { BinaryProtocol.writeString(out, failure); } } @Override protected void read(InputStream in) throws IOException { int count = BinaryProtocol.readInt(in); failures.clear(); for (int i = 0; i < count; i++) { failures.add(BinaryProtocol.readString(in)); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryListProbesCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Binary implementation of the ListProbesCommand. * This command is used to list active BTrace probes. */ public class BinaryListProbesCommand extends BinaryCommand { private final List probes = new ArrayList<>(); static { // Register this command type BinaryCommand.registerCommand(LIST_PROBES, BinaryListProbesCommand::new); } public BinaryListProbesCommand() { super(LIST_PROBES, true); } public void setProbes(Collection probes) { this.probes.clear(); this.probes.addAll(probes); } public List getProbes() { return new ArrayList<>(probes); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeInt(out, probes.size()); for (String probe : probes) { BinaryProtocol.writeString(out, probe); } } @Override protected void read(InputStream in) throws IOException { int numProbes = BinaryProtocol.readInt(in); probes.clear(); for (int i = 0; i < numProbes; i++) { probes.add(BinaryProtocol.readString(in)); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryMessageCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; /** * Binary implementation of the MessageCommand with compression support. * This command is used to send messages from the BTrace agent to the client. */ public class BinaryMessageCommand extends BinaryCommand { // Compression threshold in bytes private static final int COMPRESSION_THRESHOLD = 1024; // Message content private String message; private boolean urgent; private long timestamp; static { // Register this command type BinaryCommand.registerCommand(MESSAGE, BinaryMessageCommand::new); } public BinaryMessageCommand(long timestamp, String message, boolean urgent) { super(MESSAGE, urgent); this.message = message; this.urgent = urgent; this.timestamp = timestamp; } public BinaryMessageCommand(String message, boolean urgent) { this(System.currentTimeMillis(), message, urgent); } public BinaryMessageCommand(String message) { this(System.currentTimeMillis(), message, false); } public BinaryMessageCommand() { this(null); } @Override protected void write(OutputStream out) throws IOException { // Write timestamp BinaryProtocol.writeLong(out, timestamp); // Write urgent flag BinaryProtocol.writeBoolean(out, urgent); // Convert message to bytes byte[] messageBytes = message != null ? message.getBytes(StandardCharsets.UTF_8) : new byte[0]; // Determine if compression should be used boolean compress = messageBytes.length > COMPRESSION_THRESHOLD; BinaryProtocol.writeBoolean(out, compress); if (compress) { // Compress the message ByteArrayOutputStream compressedBytes = new ByteArrayOutputStream(); DeflaterOutputStream deflaterStream = new DeflaterOutputStream( compressedBytes, new Deflater(Deflater.BEST_SPEED) ); deflaterStream.write(messageBytes); deflaterStream.finish(); deflaterStream.close(); // Write the compressed bytes byte[] compressedData = compressedBytes.toByteArray(); BinaryProtocol.writeInt(out, messageBytes.length); // Original size BinaryProtocol.writeInt(out, compressedData.length); // Compressed size out.write(compressedData); } else { // Write uncompressed message BinaryProtocol.writeInt(out, messageBytes.length); if (messageBytes.length > 0) { out.write(messageBytes); } } } @Override protected void read(InputStream in) throws IOException { // Read timestamp timestamp = BinaryProtocol.readLong(in); // Read urgent flag urgent = BinaryProtocol.readBoolean(in); if (urgent) { setUrgent(); } // Check if the message is compressed boolean compressed = BinaryProtocol.readBoolean(in); if (compressed) { // Read the original and compressed sizes int originalSize = BinaryProtocol.readInt(in); int compressedSize = BinaryProtocol.readInt(in); // Read and decompress the message byte[] compressedData = new byte[compressedSize]; BinaryProtocol.readFully(in, compressedData); // Create an inflater to decompress InflaterInputStream inflaterStream = new InflaterInputStream( new ByteArrayInputStream(compressedData), new Inflater() ); // Read the decompressed data byte[] decompressedData = new byte[originalSize]; BinaryProtocol.readFully(inflaterStream, decompressedData); // Convert to string message = new String(decompressedData, StandardCharsets.UTF_8); } else { // Read uncompressed message int length = BinaryProtocol.readInt(in); if (length > 0) { byte[] messageBytes = new byte[length]; BinaryProtocol.readFully(in, messageBytes); message = new String(messageBytes, StandardCharsets.UTF_8); } else { message = ""; } } } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public long getTimestamp() { return timestamp; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryNumberDataCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Binary implementation of the NumberDataCommand. * This command is used to send numeric data from the BTrace agent to the client. */ public class BinaryNumberDataCommand extends BinaryDataCommand { private Number value; private static final NumberEncoding ENCODING = new NumberEncoding( (byte)0, (byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6); static { // Register this command type BinaryCommand.registerCommand(NUMBER, BinaryNumberDataCommand::new); } public BinaryNumberDataCommand(String name, Number value) { super(NUMBER, name); this.value = value; } public BinaryNumberDataCommand() { this(null, null); } @Override protected void write(OutputStream out) throws IOException { // Write the name super.write(out); // Write the value type + payload via unified encoding ENCODING.writeNumber(out, value); } @Override protected void read(InputStream in) throws IOException { // Read the name super.read(in); // Read the value via unified encoding value = ENCODING.readNumber(in); } public Number getValue() { return value; } public void setValue(Number value) { this.value = value; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryNumberMapDataCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedHashMap; import java.util.Map; /** * Binary implementation of the NumberMapDataCommand. * This command is used to send numeric map data from the BTrace agent to the client. */ public class BinaryNumberMapDataCommand extends BinaryDataCommand { private Map data = new LinkedHashMap<>(); private static final NumberEncoding ENCODING = new NumberEncoding( (byte)0, (byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6); static { // Register this command type BinaryCommand.registerCommand(NUMBER_MAP, BinaryNumberMapDataCommand::new); } public BinaryNumberMapDataCommand(String name, Map data) { super(NUMBER_MAP, name); if (data != null) { this.data.putAll(data); } } public BinaryNumberMapDataCommand() { this(null, null); } @Override protected void write(OutputStream out) throws IOException { // Write the name super.write(out); // Write the map size BinaryProtocol.writeInt(out, data.size()); // Write each map entry for (Map.Entry entry : data.entrySet()) { BinaryProtocol.writeString(out, entry.getKey()); ENCODING.writeNumber(out, entry.getValue()); } } @Override protected void read(InputStream in) throws IOException { // Read the name super.read(in); // Read the map size int size = BinaryProtocol.readInt(in); // Clear any existing data data.clear(); // Read each map entry for (int i = 0; i < size; i++) { String key = BinaryProtocol.readString(in); Number value = ENCODING.readNumber(in); data.put(key, value); } } public Map getData() { return new LinkedHashMap<>(data); } public void setData(Map data) { this.data.clear(); if (data != null) { this.data.putAll(data); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryProtocol.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; /** * A utility class for binary serialization and deserialization. * This replaces the default Java serialization with a more efficient binary protocol. */ public class BinaryProtocol { public static final byte VERSION = 3; // Maximum size for strings and byte arrays to prevent OOM attacks (100MB) private static final int MAX_ALLOCATION_SIZE = 100 * 1024 * 1024; // Basic read functions public static byte readByte(InputStream is) throws IOException { int value = is.read(); if (value == -1) { throw new IOException("End of stream reached"); } return (byte) value; } public static int readInt(InputStream is) throws IOException { byte[] buffer = new byte[4]; readFully(is, buffer); return ByteBuffer.wrap(buffer).getInt(); } public static long readLong(InputStream is) throws IOException { byte[] buffer = new byte[8]; readFully(is, buffer); return ByteBuffer.wrap(buffer).getLong(); } public static float readFloat(InputStream is) throws IOException { byte[] buffer = new byte[4]; readFully(is, buffer); return ByteBuffer.wrap(buffer).getFloat(); } public static double readDouble(InputStream is) throws IOException { byte[] buffer = new byte[8]; readFully(is, buffer); return ByteBuffer.wrap(buffer).getDouble(); } public static boolean readBoolean(InputStream is) throws IOException { return readByte(is) != 0; } public static String readString(InputStream is) throws IOException { int length = readInt(is); if (length == -1) { return null; } if (length == 0) { return ""; } if (length < 0 || length > MAX_ALLOCATION_SIZE) { throw new IOException("Invalid string length: " + length + " (must be between 0 and " + MAX_ALLOCATION_SIZE + ")"); } byte[] buffer = new byte[length]; readFully(is, buffer); return new String(buffer, StandardCharsets.UTF_8); } public static byte[] readByteArray(InputStream is) throws IOException { int length = readInt(is); if (length == -1) { return null; } if (length == 0) { return new byte[0]; } if (length < 0 || length > MAX_ALLOCATION_SIZE) { throw new IOException("Invalid byte array length: " + length + " (must be between 0 and " + MAX_ALLOCATION_SIZE + ")"); } byte[] buffer = new byte[length]; readFully(is, buffer); return buffer; } // Basic write functions public static void writeByte(OutputStream os, byte value) throws IOException { os.write(value); } public static void writeInt(OutputStream os, int value) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.putInt(value); os.write(buffer.array()); } public static void writeLong(OutputStream os, long value) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(8); buffer.putLong(value); os.write(buffer.array()); } public static void writeFloat(OutputStream os, float value) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.putFloat(value); os.write(buffer.array()); } public static void writeDouble(OutputStream os, double value) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(8); buffer.putDouble(value); os.write(buffer.array()); } public static void writeBoolean(OutputStream os, boolean value) throws IOException { writeByte(os, value ? (byte) 1 : (byte) 0); } public static void writeString(OutputStream os, String value) throws IOException { if (value == null) { writeInt(os, -1); return; } byte[] bytes = value.getBytes(StandardCharsets.UTF_8); writeInt(os, bytes.length); if (bytes.length > 0) { os.write(bytes); } } public static void writeByteArray(OutputStream os, byte[] value) throws IOException { if (value == null) { writeInt(os, -1); return; } writeInt(os, value.length); if (value.length > 0) { os.write(value); } } // Utility functions public static void readFully(InputStream is, byte[] buffer) throws IOException { readFully(is, buffer, 0, buffer.length); } public static void readFully(InputStream is, byte[] buffer, int offset, int length) throws IOException { int totalBytesRead = 0; while (totalBytesRead < length) { int bytesRead = is.read(buffer, offset + totalBytesRead, length - totalBytesRead); if (bytesRead == -1) { throw new IOException("End of stream reached"); } totalBytesRead += bytesRead; } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryReconnectCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; /** * Binary implementation of the ReconnectCommand. * This command is used to reconnect to a running BTrace agent. */ public class BinaryReconnectCommand extends BinaryStringCommand { public static final int STATUS_FLAG = 8; static { // Register this command type BinaryCommand.registerCommand(RECONNECT, BinaryReconnectCommand::new); } public BinaryReconnectCommand(String probeId) { super(RECONNECT, probeId); } public BinaryReconnectCommand() { this(null); } public String getProbeId() { return getPayload(); } public void setProbeId(String probeId) { setPayload(probeId); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryRenameCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; /** * Binary implementation of the RenameCommand. * This command is used to rename BTrace output files. */ public class BinaryRenameCommand extends BinaryStringCommand { static { // Register this command type BinaryCommand.registerCommand(RENAME, BinaryRenameCommand::new); } public BinaryRenameCommand(String newName) { super(RENAME, newName); } public BinaryRenameCommand() { this(null); } public String getNewName() { return getPayload(); } public void setNewName(String newName) { setPayload(newName); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryRetransformClassNotification.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Binary implementation of the RetransformClassNotification. * This command is used to notify the client about a class that's being retransformed. */ public class BinaryRetransformClassNotification extends BinaryCommand { private String className; private int index; private int total; static { // Register this command type BinaryCommand.registerCommand(RETRANSFORM_CLASS, BinaryRetransformClassNotification::new); } public BinaryRetransformClassNotification(String className, int index, int total) { super(RETRANSFORM_CLASS, true); this.className = className; this.index = index; this.total = total; } public BinaryRetransformClassNotification() { this(null, 0, 0); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeString(out, className); BinaryProtocol.writeInt(out, index); BinaryProtocol.writeInt(out, total); } @Override protected void read(InputStream in) throws IOException { className = BinaryProtocol.readString(in); index = BinaryProtocol.readInt(in); total = BinaryProtocol.readInt(in); } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryRetransformationStartNotification.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Binary implementation of the RetransformationStartNotification. * This command is used to notify the client that class retransformation is about to start. */ public class BinaryRetransformationStartNotification extends BinaryCommand { private int numClasses; static { // Register this command type BinaryCommand.registerCommand(RETRANSFORMATION_START, BinaryRetransformationStartNotification::new); } public BinaryRetransformationStartNotification(int numClasses) { super(RETRANSFORMATION_START, true); this.numClasses = numClasses; } public BinaryRetransformationStartNotification() { this(0); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeInt(out, numClasses); } @Override protected void read(InputStream in) throws IOException { numClasses = BinaryProtocol.readInt(in); } public int getNumClasses() { return numClasses; } public void setNumClasses(int numClasses) { this.numClasses = numClasses; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinarySetSettingsCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; /** * Binary implementation of the SetSettingsCommand. * This command is used to configure the BTrace agent settings. */ public class BinarySetSettingsCommand extends BinaryCommand { // Supported parameter types and their type codes private static final byte TYPE_STRING = 1; private static final byte TYPE_INTEGER = 2; private static final byte TYPE_LONG = 3; private static final byte TYPE_BOOLEAN = 4; private static final byte TYPE_FLOAT = 5; private static final byte TYPE_DOUBLE = 6; private final Map params; private static final ScalarEncoding SCALAR = new ScalarEncoding( (byte)0, TYPE_STRING, TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BOOLEAN); static { // Register this command type BinaryCommand.registerCommand(SET_PARAMS, BinarySetSettingsCommand::new); } public BinarySetSettingsCommand(Map params) { super(SET_PARAMS, true); this.params = params != null ? new HashMap<>(params) : new HashMap<>(); } public BinarySetSettingsCommand() { this(null); } public Map getParams() { return new HashMap<>(params); } @Override protected void write(OutputStream out) throws IOException { // Write the number of parameters BinaryProtocol.writeInt(out, params.size()); // Write each parameter for (Map.Entry entry : params.entrySet()) { BinaryProtocol.writeString(out, entry.getKey()); SCALAR.writeValue(out, entry.getValue()); } } @Override protected void read(InputStream in) throws IOException { // Read the number of parameters int size = BinaryProtocol.readInt(in); // Clear any existing parameters params.clear(); // Read each parameter for (int i = 0; i < size; i++) { // Read the parameter key String key = BinaryProtocol.readString(in); Object value = SCALAR.readValue(in); // Store the parameter params.put(key, value); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryStatusCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Binary implementation of the StatusCommand. * This command is used to communicate the status of BTrace operations. */ public class BinaryStatusCommand extends BinaryCommand { // Status flag constants public static final int STATUS_FLAG = 1; private int flag; private boolean success; static { // Register this command type BinaryCommand.registerCommand(STATUS, BinaryStatusCommand::new); } public BinaryStatusCommand(int flag) { this(flag, true); } public BinaryStatusCommand(int flag, boolean success) { super(STATUS, true); this.flag = flag; this.success = success; } public BinaryStatusCommand() { this(0, false); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeInt(out, flag); BinaryProtocol.writeBoolean(out, success); } @Override protected void read(InputStream in) throws IOException { flag = BinaryProtocol.readInt(in); success = BinaryProtocol.readBoolean(in); } public int getFlag() { return flag; } public void setFlag(int flag) { this.flag = flag; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryStringCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Abstract base class for commands that contain a single string payload. * This is used for simple commands like EventCommand, RenameCommand, etc. */ public abstract class BinaryStringCommand extends BinaryCommand { protected String payload; protected BinaryStringCommand(byte type, String payload) { super(type, true); this.payload = payload; } protected BinaryStringCommand(byte type) { this(type, null); } @Override protected void write(OutputStream out) throws IOException { BinaryProtocol.writeString(out, payload); } @Override protected void read(InputStream in) throws IOException { payload = BinaryProtocol.readString(in); } public String getPayload() { return payload; } public void setPayload(String payload) { this.payload = payload; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryStringMapDataCommand.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedHashMap; import java.util.Map; /** * Binary implementation of the StringMapDataCommand. * This command is used to send string map data from the BTrace agent to the client. */ public class BinaryStringMapDataCommand extends BinaryDataCommand { private Map data = new LinkedHashMap<>(); static { // Register this command type BinaryCommand.registerCommand(STRING_MAP, BinaryStringMapDataCommand::new); } public BinaryStringMapDataCommand(String name, Map data) { super(STRING_MAP, name); if (data != null) { this.data.putAll(data); } } public BinaryStringMapDataCommand() { this(null, null); } @Override protected void write(OutputStream out) throws IOException { // Write the name super.write(out); // Write the map size BinaryProtocol.writeInt(out, data.size()); // Write each map entry for (Map.Entry entry : data.entrySet()) { BinaryProtocol.writeString(out, entry.getKey()); BinaryProtocol.writeString(out, entry.getValue()); } } @Override protected void read(InputStream in) throws IOException { // Read the name super.read(in); // Read the map size int size = BinaryProtocol.readInt(in); // Clear any existing data data.clear(); // Read each map entry for (int i = 0; i < size; i++) { String key = BinaryProtocol.readString(in); String value = BinaryProtocol.readString(in); data.put(key, value); } } public Map getData() { return new LinkedHashMap<>(data); } public void setData(Map data) { this.data.clear(); if (data != null) { this.data.putAll(data); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/BinaryWireIO.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.locks.ReentrantLock; /** * Wire I/O implementation for the binary protocol. * This replaces the original WireIO class that relied on Java serialization. */ public class BinaryWireIO { public static final int VERSION = 2; // Singleton lock for stream synchronization private static final ReentrantLock writeLock = new ReentrantLock(); private BinaryWireIO() {} /** * Read a command from the input stream */ public static BinaryCommand read(InputStream in) throws IOException { // Read the protocol version byte protocolVersion = BinaryProtocol.readByte(in); if (protocolVersion != BinaryProtocol.VERSION) { throw new ProtocolVersionMismatchException(BinaryProtocol.VERSION, protocolVersion); } // Read the command type byte type = BinaryProtocol.readByte(in); // Create and read the command BinaryCommand cmd; try { cmd = BinaryCommand.createCommand(type); } catch (IllegalArgumentException e) { throw new MalformedCommandException(type, "Unknown command type", e); } try { cmd.read(in); } catch (IOException e) { throw new CommandDeserializationException(type, e); } return cmd; } /** * Write a command to the output stream */ public static void write(OutputStream out, BinaryCommand cmd) throws IOException { writeLock.lock(); try { // Write protocol version BinaryProtocol.writeByte(out, BinaryProtocol.VERSION); // Write command type BinaryProtocol.writeByte(out, cmd.getType()); // Write command data cmd.write(out); // Flush if urgent if (cmd.isUrgent()) { out.flush(); } } finally { writeLock.unlock(); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/CommandAdapter.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.DisconnectCommand; import org.openjdk.btrace.core.comm.ErrorCommand; import org.openjdk.btrace.core.comm.EventCommand; import org.openjdk.btrace.core.comm.ExitCommand; import org.openjdk.btrace.core.comm.GridDataCommand; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.ListProbesCommand; import org.openjdk.btrace.core.comm.ListFailedExtensionsCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.openjdk.btrace.core.comm.NumberDataCommand; import org.openjdk.btrace.core.comm.NumberMapDataCommand; import org.openjdk.btrace.core.comm.ReconnectCommand; import org.openjdk.btrace.core.comm.RenameCommand; import org.openjdk.btrace.core.comm.RetransformClassNotification; import org.openjdk.btrace.core.comm.RetransformationStartNotification; import org.openjdk.btrace.core.comm.SetSettingsCommand; import org.openjdk.btrace.core.comm.StatusCommand; import org.openjdk.btrace.core.comm.StringMapDataCommand; /** * Adapter for converting between binary commands and original commands. * This helps with incremental migration to the new protocol without breaking compatibility. */ public class CommandAdapter { private CommandAdapter() {} /** * Convert a binary command to an original command */ public static Command toBtraceCommand(BinaryCommand binaryCmd) { if (binaryCmd == null) { return Command.NULL; } switch (binaryCmd.getType()) { case BinaryCommand.EXIT: BinaryExitCommand exitCmd = (BinaryExitCommand) binaryCmd; return new ExitCommand(exitCmd.getExitCode()); case BinaryCommand.EVENT: BinaryEventCommand eventCmd = (BinaryEventCommand) binaryCmd; return new EventCommand(eventCmd.getEvent()); case BinaryCommand.MESSAGE: BinaryMessageCommand msgCmd = (BinaryMessageCommand) binaryCmd; return new MessageCommand(msgCmd.getTimestamp(), msgCmd.getMessage(), msgCmd.isUrgent()); case BinaryCommand.INSTRUMENT: BinaryInstrumentCommand instrCmd = (BinaryInstrumentCommand) binaryCmd; return new InstrumentCommand(instrCmd.getCode(), instrCmd.getArguments()); case BinaryCommand.ERROR: BinaryErrorCommand errCmd = (BinaryErrorCommand) binaryCmd; Throwable errorCause = null; if (errCmd.getExceptionClass() != null || errCmd.getMessage() != null || errCmd.getStackTrace() != null) { errorCause = new RemoteException( errCmd.getExceptionClass(), errCmd.getMessage(), errCmd.getStackTrace()); } return new ErrorCommand(errorCause); case BinaryCommand.RENAME: BinaryRenameCommand renameCmd = (BinaryRenameCommand) binaryCmd; return new RenameCommand(renameCmd.getNewName()); case BinaryCommand.STATUS: BinaryStatusCommand statusCmd = (BinaryStatusCommand) binaryCmd; int flag = statusCmd.getFlag(); return new StatusCommand(statusCmd.isSuccess() ? flag : -flag); case BinaryCommand.NUMBER_MAP: BinaryNumberMapDataCommand numMapCmd = (BinaryNumberMapDataCommand) binaryCmd; return new NumberMapDataCommand(numMapCmd.getName(), numMapCmd.getData()); case BinaryCommand.STRING_MAP: BinaryStringMapDataCommand strMapCmd = (BinaryStringMapDataCommand) binaryCmd; return new StringMapDataCommand(strMapCmd.getName(), strMapCmd.getData()); case BinaryCommand.NUMBER: BinaryNumberDataCommand numCmd = (BinaryNumberDataCommand) binaryCmd; return new NumberDataCommand(numCmd.getName(), numCmd.getValue()); case BinaryCommand.GRID_DATA: BinaryGridDataCommand gridCmd = (BinaryGridDataCommand) binaryCmd; GridDataCommand cmd = new GridDataCommand( gridCmd.getName(), gridCmd.getColumnNames(), gridCmd.getData()); return cmd; case BinaryCommand.RETRANSFORMATION_START: BinaryRetransformationStartNotification retransStartCmd = (BinaryRetransformationStartNotification) binaryCmd; return new RetransformationStartNotification(retransStartCmd.getNumClasses()); case BinaryCommand.RETRANSFORM_CLASS: BinaryRetransformClassNotification retransClassCmd = (BinaryRetransformClassNotification) binaryCmd; return new RetransformClassNotification(retransClassCmd.getClassName()); case BinaryCommand.SET_PARAMS: BinarySetSettingsCommand setParamsCmd = (BinarySetSettingsCommand) binaryCmd; return new SetSettingsCommand(setParamsCmd.getParams()); case BinaryCommand.LIST_PROBES: BinaryListProbesCommand listProbesCmd = (BinaryListProbesCommand) binaryCmd; ListProbesCommand listCmd = new ListProbesCommand(); listCmd.setProbes(listProbesCmd.getProbes()); return listCmd; case BinaryCommand.LIST_FAILED_EXTENSIONS: BinaryListFailedExtensionsCommand listFailedCmd = (BinaryListFailedExtensionsCommand) binaryCmd; ListFailedExtensionsCommand failedCmd = new ListFailedExtensionsCommand(); failedCmd.setFailedExtensionsList(listFailedCmd.getFailures()); return failedCmd; case BinaryCommand.DISCONNECT: BinaryDisconnectCommand disconnectCmd = (BinaryDisconnectCommand) binaryCmd; return new DisconnectCommand(disconnectCmd.getProbeId()); case BinaryCommand.RECONNECT: BinaryReconnectCommand reconnectCmd = (BinaryReconnectCommand) binaryCmd; return new ReconnectCommand(reconnectCmd.getProbeId()); default: throw new IllegalArgumentException( "Unsupported command type for conversion: " + binaryCmd.getType()); } } /** * Convert an original command to a binary command */ public static BinaryCommand toBinaryCommand(Command originalCmd) { if (originalCmd == Command.NULL) { return null; } switch (originalCmd.getType()) { case Command.EXIT: ExitCommand exitCmd = (ExitCommand) originalCmd; return new BinaryExitCommand(exitCmd.getExitCode()); case Command.EVENT: EventCommand eventCmd = (EventCommand) originalCmd; return new BinaryEventCommand(eventCmd.getEvent()); case Command.MESSAGE: MessageCommand msgCmd = (MessageCommand) originalCmd; return new BinaryMessageCommand(msgCmd.getTime(), msgCmd.getMessage(), originalCmd.isUrgent()); case Command.INSTRUMENT: InstrumentCommand instrCmd = (InstrumentCommand) originalCmd; return new BinaryInstrumentCommand(instrCmd.getCode(), instrCmd.getArguments()); case Command.ERROR: ErrorCommand errCmd = (ErrorCommand) originalCmd; Throwable cause = errCmd.getCause(); String exceptionClass = null; String message = null; String stackTrace = null; if (cause != null) { exceptionClass = cause.getClass().getName(); message = cause.getMessage(); StringWriter writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); cause.printStackTrace(printWriter); printWriter.flush(); stackTrace = writer.toString(); } return new BinaryErrorCommand(exceptionClass, message, stackTrace); case Command.RENAME: RenameCommand renameCmd = (RenameCommand) originalCmd; return new BinaryRenameCommand(renameCmd.getNewName()); case Command.STATUS: StatusCommand statusCmd = (StatusCommand) originalCmd; return new BinaryStatusCommand(statusCmd.getFlag(), statusCmd.isSuccess()); case Command.NUMBER_MAP: NumberMapDataCommand numMapCmd = (NumberMapDataCommand) originalCmd; Map numMap = new LinkedHashMap<>(); for (Map.Entry entry : numMapCmd.getData().entrySet()) { if (entry.getValue() instanceof Number) { numMap.put(entry.getKey(), (Number) entry.getValue()); } } return new BinaryNumberMapDataCommand(numMapCmd.getName(), numMap); case Command.STRING_MAP: StringMapDataCommand strMapCmd = (StringMapDataCommand) originalCmd; Map strMap = new LinkedHashMap<>(); for (Map.Entry entry : strMapCmd.getData().entrySet()) { strMap.put(entry.getKey(), String.valueOf(entry.getValue())); } return new BinaryStringMapDataCommand(strMapCmd.getName(), strMap); case Command.NUMBER: NumberDataCommand numCmd = (NumberDataCommand) originalCmd; return new BinaryNumberDataCommand(numCmd.getName(), numCmd.getValue()); case Command.GRID_DATA: GridDataCommand gridCmd = (GridDataCommand) originalCmd; return new BinaryGridDataCommand( gridCmd.getName(), gridCmd.getColumnNames(), gridCmd.getData()); case Command.RETRANSFORMATION_START: RetransformationStartNotification retransStartCmd = (RetransformationStartNotification) originalCmd; return new BinaryRetransformationStartNotification(retransStartCmd.getNumClasses()); case Command.RETRANSFORM_CLASS: RetransformClassNotification retransClassCmd = (RetransformClassNotification) originalCmd; return new BinaryRetransformClassNotification(retransClassCmd.getClassName(), 0, 0); case Command.SET_PARAMS: SetSettingsCommand setParamsCmd = (SetSettingsCommand) originalCmd; return new BinarySetSettingsCommand(setParamsCmd.getParams()); case Command.LIST_PROBES: ListProbesCommand listProbesCmd = (ListProbesCommand) originalCmd; BinaryListProbesCommand listCmd = new BinaryListProbesCommand(); listCmd.setProbes(listProbesCmd.getProbes()); return listCmd; case Command.DISCONNECT: DisconnectCommand disconnectCmd = (DisconnectCommand) originalCmd; return new BinaryDisconnectCommand(disconnectCmd.getProbeId()); case Command.RECONNECT: ReconnectCommand reconnectCmd = (ReconnectCommand) originalCmd; return new BinaryReconnectCommand(reconnectCmd.getProbeId()); case Command.LIST_FAILED_EXTENSIONS: ListFailedExtensionsCommand listFailedCmd = (ListFailedExtensionsCommand) originalCmd; BinaryListFailedExtensionsCommand binaryFailedCmd = new BinaryListFailedExtensionsCommand(); binaryFailedCmd.setFailures(listFailedCmd.getFailedExtensions()); return binaryFailedCmd; default: throw new IllegalArgumentException( "Unsupported command type for conversion: " + originalCmd.getType()); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/CommandDeserializationException.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; /** * Exception thrown when deserialization of a command fails. */ public class CommandDeserializationException extends IOException { private final byte commandType; public CommandDeserializationException(byte commandType, String message, Throwable cause) { super(String.format("Failed to deserialize command (type=%d): %s", commandType, message), cause); this.commandType = commandType; } public CommandDeserializationException(byte commandType, Throwable cause) { super(String.format("Failed to deserialize command (type=%d)", commandType), cause); this.commandType = commandType; } public byte getCommandType() { return commandType; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/MalformedCommandException.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; /** * Exception thrown when a command has invalid structure or data. */ public class MalformedCommandException extends IOException { private final byte commandType; public MalformedCommandException(byte commandType, String message) { super(String.format("Malformed command (type=%d): %s", commandType, message)); this.commandType = commandType; } public MalformedCommandException(byte commandType, String message, Throwable cause) { super(String.format("Malformed command (type=%d): %s", commandType, message), cause); this.commandType = commandType; } public byte getCommandType() { return commandType; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/NumberEncoding.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; /** * Small helper to unify encoding of numeric values (null, int, long, float, double) * across binary commands that share the same type code mapping. */ final class NumberEncoding { private final byte nullCode; private final byte intCode; private final byte longCode; private final byte floatCode; private final byte doubleCode; private final byte bigIntegerCode; private final byte bigDecimalCode; NumberEncoding( byte nullCode, byte intCode, byte longCode, byte floatCode, byte doubleCode, byte bigIntegerCode, byte bigDecimalCode) { this.nullCode = nullCode; this.intCode = intCode; this.longCode = longCode; this.floatCode = floatCode; this.doubleCode = doubleCode; this.bigIntegerCode = bigIntegerCode; this.bigDecimalCode = bigDecimalCode; } void writeNumber(OutputStream out, Number value) throws IOException { if (value == null) { BinaryProtocol.writeByte(out, nullCode); return; } if (value instanceof Integer) { BinaryProtocol.writeByte(out, intCode); BinaryProtocol.writeInt(out, (Integer) value); } else if (value instanceof Long) { BinaryProtocol.writeByte(out, longCode); BinaryProtocol.writeLong(out, (Long) value); } else if (value instanceof Float) { BinaryProtocol.writeByte(out, floatCode); BinaryProtocol.writeFloat(out, (Float) value); } else if (value instanceof Double) { BinaryProtocol.writeByte(out, doubleCode); BinaryProtocol.writeDouble(out, (Double) value); } else if (value instanceof BigInteger) { BinaryProtocol.writeByte(out, bigIntegerCode); BinaryProtocol.writeString(out, value.toString()); } else if (value instanceof BigDecimal) { BinaryProtocol.writeByte(out, bigDecimalCode); BinaryProtocol.writeString(out, value.toString()); } else { // Default to long for other number implementations BinaryProtocol.writeByte(out, longCode); BinaryProtocol.writeLong(out, value.longValue()); } } Number readNumber(InputStream in) throws IOException { byte type = BinaryProtocol.readByte(in); if (type == nullCode) { return null; } else if (type == intCode) { return BinaryProtocol.readInt(in); } else if (type == longCode) { return BinaryProtocol.readLong(in); } else if (type == floatCode) { return BinaryProtocol.readFloat(in); } else if (type == doubleCode) { return BinaryProtocol.readDouble(in); } else if (type == bigIntegerCode) { return new BigInteger(BinaryProtocol.readString(in)); } else if (type == bigDecimalCode) { return new BigDecimal(BinaryProtocol.readString(in)); } else { throw new IOException("Unsupported number type: " + type); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/ProtocolVersionMismatchException.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; /** * Exception thrown when the protocol version in the stream doesn't match * the expected version. */ public class ProtocolVersionMismatchException extends IOException { private final int expectedVersion; private final int actualVersion; public ProtocolVersionMismatchException(int expectedVersion, int actualVersion) { super(String.format("Protocol version mismatch: expected %d, got %d", expectedVersion, actualVersion)); this.expectedVersion = expectedVersion; this.actualVersion = actualVersion; } public int getExpectedVersion() { return expectedVersion; } public int getActualVersion() { return actualVersion; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/README.md ================================================ # BTrace Binary Protocol (v2) This package contains a new binary protocol implementation for BTrace that replaces the original Java serialization-based protocol with a more efficient binary format. ## Benefits - **Improved Performance**: Custom binary serialization is significantly faster than Java serialization - **Reduced Memory Usage**: Binary format is more compact than Java serialization - **Compression Support**: Large messages and code payloads are automatically compressed - **Thread Safety**: Uses ReentrantLock instead of synchronized blocks for better scalability - **Versioning**: Protocol includes version information for future compatibility - **Extensibility**: Registry pattern makes it easy to add new command types - **Backward Compatibility**: Adapter layer allows gradual migration ## Protocol Format Each command in the binary protocol has the following format: ``` +----------------+----------------+--------------------+ | Protocol Version (1 byte) | Command Type (1 byte) | Command Data | +----------------+----------------+--------------------+ ``` The Command Data format depends on the Command Type and is defined by each command implementation. ## Command Types The binary protocol supports all the command types from the original protocol: | Type | Command | Description | |------|---------|-------------| | 0 | ERROR | Error notification | | 1 | EVENT | Event trigger | | 2 | EXIT | Exit command | | 3 | INSTRUMENT | Instrumentation code | | 4 | MESSAGE | Text message | | 5 | RENAME | Rename command | | 6 | STATUS | Status information | | 7 | NUMBER_MAP | Number map data | | 8 | STRING_MAP | String map data | | 9 | NUMBER | Number data | | 10 | GRID_DATA | Grid/tabular data | | 11 | RETRANSFORMATION_START | Class retransformation start | | 12 | RETRANSFORM_CLASS | Class retransformation notification | | 13 | SET_PARAMS | Settings parameters | | 14 | LIST_PROBES | List probes command | | 15 | DISCONNECT | Disconnect command | | 16 | RECONNECT | Reconnect command | | 17 | LIST_FAILED_EXTENSIONS | List failed extensions command | ## Data Types The binary protocol supports the following data types: - byte, int, long, float, double - boolean - String (UTF-8 encoded with length prefix) - byte[] (with length prefix) - Nested data structures (maps, lists, etc.) ## Compression Large payloads (like message text and instrumentation code) are automatically compressed using Java's Deflater/Inflater with BEST_SPEED setting. The compression threshold is configurable. ## Migration Use the `CommandAdapter` class to convert between binary commands and original commands: ```java // Convert original command to binary command BinaryCommand binaryCmd = CommandAdapter.toBinaryCommand(originalCmd); // Convert binary command to original command Command originalCmd = CommandAdapter.toBtraceCommand(binaryCmd); ``` For applications using the core BTrace API directly, use `BinaryClient` instead of the original client: ```java // Create a binary client BinaryClient client = new BinaryClient(inputStream, outputStream, commandListener); // Send a command client.sendMessage("Hello, World!", true); // Process commands client.commandLoop(); ``` ## Performance Comparison Performance tests show that the binary protocol is significantly more efficient than the original Java serialization-based protocol: | Command | Time Improvement | Size Improvement | |---------|-----------------|------------------| | InstrumentCommand | 3-5x faster | 2-3x smaller | | MessageCommand | 4-6x faster | 3-5x smaller (with compression) | ## Implementation The implementation follows a clean, object-oriented design: - `BinaryProtocol`: Low-level binary serialization utilities - `BinaryCommand`: Base class for all commands - `BinaryWireIO`: Wire protocol implementation - `BinaryClient`: Client wrapper for the binary protocol - Command implementations: One class per command type - `CommandAdapter`: Conversion between binary and original commands ## Future Enhancements - Add more compression algorithms (LZ4, Snappy) - Implement batching for multiple commands - Add flow control and backpressure handling - Add direct ByteBuffer support for zero-copy operations - Implement multiplexing for multiple concurrent clients ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/RemoteException.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.PrintStream; import java.io.PrintWriter; class RemoteException extends RuntimeException { private final String exceptionClass; private final String remoteStackTrace; RemoteException(String exceptionClass, String message, String remoteStackTrace) { super(message); this.exceptionClass = exceptionClass; this.remoteStackTrace = remoteStackTrace; } @Override public void printStackTrace(PrintWriter s) { if (remoteStackTrace != null) { s.print(remoteStackTrace); return; } super.printStackTrace(s); } @Override public void printStackTrace(PrintStream s) { if (remoteStackTrace != null) { s.print(remoteStackTrace); return; } super.printStackTrace(s); } @Override public String toString() { if (exceptionClass == null) { return super.toString(); } String message = getMessage(); return message == null ? exceptionClass : exceptionClass + ": " + message; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/comm/v2/ScalarEncoding.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Helper to encode/decode common scalar types with configurable type codes. * Supported: null, String, Integer, Long, Float, Double, Boolean. */ final class ScalarEncoding { private final byte nullCode; private final byte stringCode; private final byte intCode; private final byte longCode; private final byte floatCode; private final byte doubleCode; private final byte booleanCode; ScalarEncoding( byte nullCode, byte stringCode, byte intCode, byte longCode, byte floatCode, byte doubleCode, byte booleanCode) { this.nullCode = nullCode; this.stringCode = stringCode; this.intCode = intCode; this.longCode = longCode; this.floatCode = floatCode; this.doubleCode = doubleCode; this.booleanCode = booleanCode; } void writeValue(OutputStream out, Object value) throws IOException { if (value == null) { BinaryProtocol.writeByte(out, nullCode); return; } if (value instanceof String) { BinaryProtocol.writeByte(out, stringCode); BinaryProtocol.writeString(out, (String) value); } else if (value instanceof Integer) { BinaryProtocol.writeByte(out, intCode); BinaryProtocol.writeInt(out, (Integer) value); } else if (value instanceof Long) { BinaryProtocol.writeByte(out, longCode); BinaryProtocol.writeLong(out, (Long) value); } else if (value instanceof Float) { BinaryProtocol.writeByte(out, floatCode); BinaryProtocol.writeFloat(out, (Float) value); } else if (value instanceof Double) { BinaryProtocol.writeByte(out, doubleCode); BinaryProtocol.writeDouble(out, (Double) value); } else if (value instanceof Boolean) { BinaryProtocol.writeByte(out, booleanCode); BinaryProtocol.writeBoolean(out, (Boolean) value); } else { // Fallback: write as string BinaryProtocol.writeByte(out, stringCode); BinaryProtocol.writeString(out, value.toString()); } } Object readValue(InputStream in) throws IOException { byte type = BinaryProtocol.readByte(in); if (type == nullCode) { return null; } else if (type == stringCode) { return BinaryProtocol.readString(in); } else if (type == intCode) { return BinaryProtocol.readInt(in); } else if (type == longCode) { return BinaryProtocol.readLong(in); } else if (type == floatCode) { return BinaryProtocol.readFloat(in); } else if (type == doubleCode) { return BinaryProtocol.readDouble(in); } else if (type == booleanCode) { return BinaryProtocol.readBoolean(in); } else { throw new IOException("Unsupported scalar type: " + type); } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/Extension.java ================================================ package org.openjdk.btrace.core.extensions; /** * Base class for all BTrace extensions. * *

Extensions provide additional functionality to BTrace scripts beyond the core BTraceUtils API. * They declare required permissions via descriptors and implement proper lifecycle management. * *

Lifecycle: * *

    *
  1. Extension is instantiated via no-arg constructor *
  2. {@link #initialize(ExtensionContext)} is called once *
  3. Extension methods are called by BTrace script *
  4. {@link #close()} is called when script detaches *
* *

Thread Safety: Extensions must be thread-safe as they may be called concurrently from * multiple instrumented threads. * *

Example: * *

 * public class ExampleExtension extends Extension {
 *   private Socket socket;
 *
 *   @Override
 *   public void initialize(ExtensionContext ctx) {
 *     super.initialize(ctx);
 *     socket = new Socket(...);
 *   }
 *
 *   public void sendData(String data) {
 *     // Extension functionality
 *   }
 *
 *   @Override
 *   public void close() {
 *     if (socket != null) {
 *       socket.close();
 *     }
 *   }
 * }
 * 
*/ public abstract class Extension implements AutoCloseable { private volatile ExtensionContext context; /** * Initializes this extension with the provided context. * *

Called once after construction, before any extension methods are invoked. Implementations * should perform initialization that requires access to the runtime context (e.g., acquiring * resources, starting threads). * *

This method is called by the BTrace runtime and should not be invoked directly. * * @param ctx the extension context * @throws ExtensionException if initialization fails */ public void initialize(ExtensionContext ctx) throws ExtensionException { this.context = ctx; } /** * Releases resources held by this extension. * *

Called when the BTrace script detaches. Implementations must clean up all resources (close * sockets, stop threads, release buffers, etc.). * *

This method is called by the BTrace runtime and should not be invoked directly. * *

Implementations should not throw exceptions. If cleanup fails, errors should be logged but * not propagated. */ @Override public void close() { // Default: no cleanup needed } /** * Returns the extension context. * * @return the context provided at initialization * @throws IllegalStateException if called before {@link #initialize(ExtensionContext)} */ protected final ExtensionContext getContext() { if (context == null) { throw new IllegalStateException("Extension not initialized"); } return context; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ExtensionContext.java ================================================ package org.openjdk.btrace.core.extensions; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.comm.Command; /** * Context provided to extensions at initialization. * *

Extensions use this context to access BTrace runtime services such as messaging, script * arguments, and permission checking. */ public interface ExtensionContext { /** * Sends a message to the BTrace client. * * @param message the message text */ void send(String message); /** * Sends a command to the BTrace client. * * @param command the command to send */ void send(Command command); /** * Returns the script arguments. * * @return script arguments map */ ArgsMap getArgs(); /** * Returns the fully qualified name of the script class using this extension. * * @return script class name */ String getScriptClassName(); /** * Returns the permissions granted to the script. * * @return permission set */ PermissionSet getPermissions(); /** * Checks if the script has the specified permission. * * @param permission the permission to check * @return true if the permission is granted */ boolean hasPermission(Permission permission); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ExtensionDescriptor.java ================================================ package org.openjdk.btrace.core.extensions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Describes metadata for a BTrace extension (package-level, optional). * *

Place this annotation in your API module's `package-info.java` to document the extension. * Manifest entries generated by the Gradle plugin remain the single source of truth. */ @Target(ElementType.PACKAGE) @Retention(RetentionPolicy.RUNTIME) public @interface ExtensionDescriptor { /** * The unique name of this extension. * * @return extension name */ String name(); /** * The version of this extension. * * @return version string (e.g., "1.0", "2.1.0") */ String version(); /** * Human-readable description of this extension. * * @return description text */ String description() default ""; /** * Minimum BTrace version required by this extension. * * @return minimum BTrace version (e.g., "2.1") */ String minBTraceVersion() default ""; /** * Other extensions that must be available for this extension to function. * * @return array of required extension classes */ Class[] dependencies() default {}; /** * Permissions required by this extension as a whole. * *

These are combined with permissions declared on individual services via * {@link ServiceDescriptor#permissions()} when computing the effective permission set. */ Permission[] permissions() default {}; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ExtensionException.java ================================================ package org.openjdk.btrace.core.extensions; /** * Exception thrown when extension operations fail. * *

This exception is used for extension-related errors including: * *

    *
  • Missing or invalid extension annotations *
  • Permission violations *
  • Initialization failures *
  • Missing dependencies *
*/ public class ExtensionException extends RuntimeException { /** * Constructs an exception with the specified message. * * @param message the error message */ public ExtensionException(String message) { super(message); } /** * Constructs an exception with the specified message and cause. * * @param message the error message * @param cause the underlying cause */ public ExtensionException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ExtensionMeta.java ================================================ package org.openjdk.btrace.core.extensions; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * Metadata extracted from an extension class. * *

This class holds immutable metadata parsed from package-level {@link ExtensionDescriptor} * (if available). */ public final class ExtensionMeta { private final Class extensionClass; private final String name; private final String version; private final String description; private final String minBTraceVersion; private final PermissionSet requiredPermissions; private final Set> dependencies; private ExtensionMeta( Class extensionClass, String name, String version, String description, String minBTraceVersion, PermissionSet requiredPermissions, Set> dependencies) { this.extensionClass = extensionClass; this.name = name; this.version = version; this.description = description; this.minBTraceVersion = minBTraceVersion; this.requiredPermissions = requiredPermissions; this.dependencies = dependencies; } /** * Extracts metadata from an extension class. * * @param extensionClass the extension class * @return extracted metadata * Builds metadata from package-level {@link ExtensionDescriptor} when present. */ public static ExtensionMeta from(Class extensionClass) { // Prefer package-level descriptor for identity and extension-level permissions Package pkg = extensionClass.getPackage(); ExtensionDescriptor pkgDesc = pkg != null ? pkg.getAnnotation(ExtensionDescriptor.class) : null; String name = (pkgDesc != null && !pkgDesc.name().isEmpty()) ? pkgDesc.name() : extensionClass.getSimpleName(); String version = (pkgDesc != null) ? pkgDesc.version() : ""; String description = (pkgDesc != null) ? pkgDesc.description() : ""; String minBTraceVersion = (pkgDesc != null) ? pkgDesc.minBTraceVersion() : ""; // Extract required permissions (pkg-level) Set permissions = new HashSet<>(); if (pkgDesc != null) { for (Permission p : pkgDesc.permissions()) { if (p != null) permissions.add(p); } } PermissionSet permissionSet = permissions.isEmpty() ? PermissionSet.empty() : PermissionSet.of(permissions.toArray(new Permission[0])); return new ExtensionMeta( extensionClass, name, version, description, minBTraceVersion, permissionSet, Collections.emptySet()); } /** * Returns the extension class. * * @return extension class */ public Class getExtensionClass() { return extensionClass; } /** * Returns the extension name. * * @return name */ public String getName() { return name; } /** * Returns the extension version. * * @return version string */ public String getVersion() { return version; } /** * Returns the extension description. * * @return description text */ public String getDescription() { return description; } /** * Returns the minimum required BTrace version. * * @return minimum BTrace version */ public String getMinBTraceVersion() { return minBTraceVersion; } /** * Returns the required permissions. * * @return permission set */ public PermissionSet getRequiredPermissions() { return requiredPermissions; } /** * Returns the extension dependencies. * * @return set of required extension classes */ public Set> getDependencies() { return dependencies; } @Override public String toString() { return "ExtensionMeta{" + "name='" + name + '\'' + ", version='" + version + '\'' + ", permissions=" + requiredPermissions + '}'; } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/Permission.java ================================================ package org.openjdk.btrace.core.extensions; /** * Permissions that can be requested by BTrace extensions. * *

Permissions are organized into three tiers: * *

    *
  • Default - Always granted, core BTrace functionality *
  • Standard - Granted unless explicitly restricted *
  • Privileged - Require explicit user consent *
*/ public enum Permission { // Default permissions - always granted /** Permission to send messages to the BTrace client */ MESSAGING, /** Permission to use aggregation functions */ AGGREGATION, /** Permission to create and use JFR events */ JFR_EVENTS, /** Permission to use profiling functions */ PROFILING, // Standard permissions - granted unless restricted /** Permission to read files (limited to specific paths) */ FILE_READ, /** Permission to read system properties */ SYSTEM_PROPS, /** Permission to read thread information */ THREAD_INFO, /** Permission to read memory and GC information */ MEMORY_INFO, // Privileged permissions - require explicit consent /** Permission to write to files */ FILE_WRITE, /** Permission for network I/O (sockets, HTTP) */ NETWORK, /** Permission to create and manage threads */ THREADS, /** Permission to call native code (JNI, Unsafe) */ NATIVE, /** Permission to execute external processes */ EXEC, /** Permission to use reflection */ REFLECTION, /** Permission to access classloaders */ CLASSLOADER, /** Permission for unlimited buffer allocation */ UNLIMITED_MEMORY; /** * Returns whether this is a default permission. * * @return true if this permission is always granted */ public boolean isDefault() { return ordinal() <= PROFILING.ordinal(); } /** * Returns whether this is a standard permission. * * @return true if this permission is granted unless restricted */ public boolean isStandard() { return ordinal() > PROFILING.ordinal() && ordinal() <= MEMORY_INFO.ordinal(); } /** * Returns whether this is a privileged permission. * * @return true if this permission requires explicit consent */ public boolean isPrivileged() { return ordinal() > MEMORY_INFO.ordinal(); } /** * Returns a description of the security risk associated with this permission. * * @return risk description for user warning */ public String getRiskDescription() { switch (this) { case MESSAGING: return "Send messages to BTrace client. Low risk."; case AGGREGATION: return "Use aggregation functions. Low risk."; case JFR_EVENTS: return "Create JFR events. Low risk."; case PROFILING: return "Use profiling functions. Low risk."; case FILE_READ: return "Read files from disk. Risk: Information disclosure."; case SYSTEM_PROPS: return "Read system properties. Risk: Information disclosure."; case THREAD_INFO: return "Read thread information. Risk: Information disclosure."; case MEMORY_INFO: return "Read memory/GC information. Risk: Information disclosure."; case FILE_WRITE: return "Write files to disk. Risk: Data modification, disk exhaustion."; case NETWORK: return "Network I/O (sockets, HTTP). Risk: Data exfiltration, remote connections."; case THREADS: return "Create and manage threads. Risk: Resource exhaustion, concurrent operations."; case NATIVE: return "Call native code (JNI, Unsafe). Risk: JVM crashes, memory corruption."; case EXEC: return "Execute external processes. Risk: Arbitrary command execution."; case REFLECTION: return "Use reflection. Risk: Bypass access controls."; case CLASSLOADER: return "Access classloaders. Risk: Load arbitrary code."; case UNLIMITED_MEMORY: return "Unlimited buffer allocation. Risk: Memory exhaustion."; default: return "Unknown permission."; } } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/PermissionSet.java ================================================ package org.openjdk.btrace.core.extensions; import java.util.EnumSet; import java.util.Iterator; /** * Immutable set of permissions. * *

This class provides an efficient, allocation-free way to check permissions and create derived * permission sets. */ public final class PermissionSet implements Iterable { private static final PermissionSet EMPTY = new PermissionSet(EnumSet.noneOf(Permission.class)); private static final PermissionSet ALL = new PermissionSet(EnumSet.allOf(Permission.class)); private static final PermissionSet DEFAULT; private static final PermissionSet STANDARD; static { EnumSet defaultPerms = EnumSet.noneOf(Permission.class); EnumSet standardPerms = EnumSet.noneOf(Permission.class); for (Permission p : Permission.values()) { if (p.isDefault()) { defaultPerms.add(p); } if (p.isDefault() || p.isStandard()) { standardPerms.add(p); } } DEFAULT = new PermissionSet(defaultPerms); STANDARD = new PermissionSet(standardPerms); } private final EnumSet permissions; private PermissionSet(EnumSet permissions) { this.permissions = EnumSet.copyOf(permissions); } /** * Returns an empty permission set. * * @return empty permission set */ public static PermissionSet empty() { return EMPTY; } /** * Returns a permission set containing all permissions. * * @return permission set with all permissions */ public static PermissionSet all() { return ALL; } /** * Returns a permission set containing default permissions. * * @return permission set with default permissions */ public static PermissionSet defaults() { return DEFAULT; } /** * Returns a permission set containing default and standard permissions. * * @return permission set with default and standard permissions */ public static PermissionSet standard() { return STANDARD; } /** * Returns a permission set containing the specified permissions. * * @param permissions the permissions to include * @return new permission set */ public static PermissionSet of(Permission... permissions) { if (permissions.length == 0) { return EMPTY; } EnumSet set = EnumSet.noneOf(Permission.class); for (Permission p : permissions) { set.add(p); } return new PermissionSet(set); } /** * Checks if this set contains the specified permission. * * @param permission the permission to check * @return true if the permission is present */ public boolean has(Permission permission) { return permissions.contains(permission); } /** * Checks if this set contains all of the specified permissions. * * @param other the permission set to check * @return true if all permissions are present */ public boolean hasAll(PermissionSet other) { return permissions.containsAll(other.permissions); } /** * Returns a new permission set with the specified permissions added. * * @param toAdd the permissions to add * @return new permission set */ public PermissionSet with(Permission... toAdd) { if (toAdd.length == 0) { return this; } EnumSet newSet = EnumSet.copyOf(permissions); for (Permission p : toAdd) { newSet.add(p); } return new PermissionSet(newSet); } /** * Returns a new permission set with the specified permissions removed. * * @param toRemove the permissions to remove * @return new permission set */ public PermissionSet without(Permission... toRemove) { if (toRemove.length == 0) { return this; } EnumSet newSet = EnumSet.copyOf(permissions); for (Permission p : toRemove) { newSet.remove(p); } return new PermissionSet(newSet); } /** * Returns the number of permissions in this set. * * @return permission count */ public int size() { return permissions.size(); } /** * Returns whether this set is empty. * * @return true if no permissions are present */ public boolean isEmpty() { return permissions.isEmpty(); } @Override public Iterator iterator() { return permissions.iterator(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PermissionSet)) { return false; } PermissionSet other = (PermissionSet) obj; return permissions.equals(other.permissions); } @Override public int hashCode() { return permissions.hashCode(); } @Override public String toString() { return permissions.toString(); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ServiceDescriptor.java ================================================ package org.openjdk.btrace.core.extensions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marks a type as an injectable BTrace service API. * *

Annotate public top-level interfaces in an extension's API module that are intended to be * injected into BTrace scripts via {@code @Injected}. * *

The BTrace Gradle extension plugin scans for this annotation in the API output and generates * the appropriate manifest entries (BTrace-Extension-Services). The compiler/verifier framework can * rely on the agent-provided service declaration registry (populated from these manifests) to * enforce that only declared service APIs are injected.

* *

You may also declare service-level permissions here. These combine with extension-level * permissions from {@link ExtensionDescriptor#permissions()} to form the effective permission set.

*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ServiceDescriptor { Permission[] permissions() default {}; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ErrorHandler.java ================================================ package org.openjdk.btrace.core.handlers; import java.lang.reflect.Method; public final class ErrorHandler { public final String method; public ErrorHandler(String method) { this.method = method; } public Method getMethod(Class clz) throws NoSuchMethodException { return clz.getMethod(method, Throwable.class); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/handlers/EventHandler.java ================================================ package org.openjdk.btrace.core.handlers; import java.lang.reflect.Method; public final class EventHandler { public static final String ALL_EVENTS = ""; public final String method; private final String event; public EventHandler(String method, String event) { this.method = method; this.event = event; } public String getEvent() { return event != null ? event : ALL_EVENTS; } public Method getMethod(Class clz) throws NoSuchMethodException { return clz.getMethod(method); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ExitHandler.java ================================================ package org.openjdk.btrace.core.handlers; import java.lang.reflect.Method; public class ExitHandler { public final String method; public ExitHandler(String method) { this.method = method; } public Method getMethod(Class clz) throws NoSuchMethodException { return clz.getMethod(method, int.class); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/handlers/LowMemoryHandler.java ================================================ package org.openjdk.btrace.core.handlers; import java.lang.management.MemoryUsage; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class LowMemoryHandler { public final String method; public final String pool; public final String thresholdProperty; public final long threshold; public final boolean trackUsage; private Method executable; public LowMemoryHandler( String method, String pool, long threshold, String thresholdProperty, boolean trackUsage) { this.method = method; this.pool = pool; this.threshold = threshold; this.thresholdProperty = thresholdProperty; this.trackUsage = trackUsage; } public synchronized Method getMethod(Class clz) throws NoSuchMethodException { if (executable == null) { executable = trackUsage ? clz.getMethod(method, MemoryUsage.class) : clz.getMethod(method); } return executable; } public void invoke(Class clz, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { getMethod(clz).invoke(clz, null, args); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/handlers/TimerHandler.java ================================================ package org.openjdk.btrace.core.handlers; import java.lang.reflect.Method; public final class TimerHandler { public final String method; public final long period; public final String periodArg; public TimerHandler(String method, long period, String periodArg) { this.method = method; this.period = period; this.periodArg = periodArg; } public Method getMethod(Class clz) throws NoSuchMethodException { return clz.getMethod(method); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/jfr/JfrEvent.java ================================================ package org.openjdk.btrace.core.jfr; @SuppressWarnings("UnusedReturnValue") public abstract class JfrEvent { public static final class Template { public static final class Field { private final String name; private final String type; private final String label; private final String description; private final String specificationName; private final String specificationValue; public Field( String name, String type, String label, String description, String specificationName, String specificationValue) { this.name = name; this.type = type; this.label = label; this.description = description; this.specificationName = specificationName; this.specificationValue = specificationValue; } public String getName() { return name; } public String getType() { return type; } public String getLabel() { return label; } public String getDescription() { return description; } public String getSpecificationName() { return specificationName; } public String getSpecificationValue() { return specificationValue; } } private final String owner; private final String name; private final String label; private final String description; private final String[] category; private final Field[] fields; private final boolean stacktrace; private final String period; private final String periodicHandler; public Template( String owner, String name, String label, String description, String[] category, Field[] fields, boolean stacktrace, String period, String periodicHandler) { this.owner = owner; this.name = name; this.label = label; this.description = description; this.category = category; this.fields = fields; this.stacktrace = stacktrace; this.period = period; this.periodicHandler = periodicHandler; } public String getOwner() { return owner; } public String getName() { return name; } public String getLabel() { return label; } public String getDescription() { return description; } public String[] getCategory() { return category; } public Field[] getFields() { return fields; } public boolean isStacktrace() { return stacktrace; } public String getPeriod() { return period; } public String getPeriodicHandler() { return periodicHandler; } } public interface Factory { JfrEvent newEvent(); } public static final JfrEvent EMPTY = new JfrEvent() { @Override public JfrEvent withValue(String fieldName, byte value) { return this; } @Override public JfrEvent withValue(String fieldName, boolean value) { return this; } @Override public JfrEvent withValue(String fieldName, char value) { return this; } @Override public JfrEvent withValue(String fieldName, short value) { return this; } @Override public JfrEvent withValue(String fieldName, int value) { return this; } @Override public JfrEvent withValue(String fieldName, float value) { return this; } @Override public JfrEvent withValue(String fieldName, long value) { return this; } @Override public JfrEvent withValue(String fieldName, double value) { return this; } @Override public JfrEvent withValue(String fieldName, String value) { return this; } @Override public void commit() {} @Override public boolean shouldCommit() { return false; } @Override public void begin() {} @Override public void end() {} }; public abstract JfrEvent withValue(String fieldName, byte value); public abstract JfrEvent withValue(String fieldName, boolean value); public abstract JfrEvent withValue(String fieldName, char value); public abstract JfrEvent withValue(String fieldName, short value); public abstract JfrEvent withValue(String fieldName, int value); public abstract JfrEvent withValue(String fieldName, float value); public abstract JfrEvent withValue(String fieldName, long value); public abstract JfrEvent withValue(String fieldName, double value); public abstract JfrEvent withValue(String fieldName, String value); public abstract void commit(); public abstract boolean shouldCommit(); public abstract void begin(); public abstract void end(); } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/types/AnyType.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.types; /** * This interface type is used in BTrace programs to tell that any reference type [object or array] * is allowed in the place where it is used. We use that for method signature matching when * signature needs to be specified loosely. Note that we don't want to use java.lang.Object - * because user may want to match java.lang.Object exactly. * * @author A. Sundararajan */ public interface AnyType { AnyType VOID = new AnyType() {}; } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceCollection.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.types; import java.util.Collection; /** * Marker interface for BTrace defined collections * * @author Jaroslav Bachorik */ public interface BTraceCollection extends Collection {} ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceDeque.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.types; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; import java.util.Iterator; /** * @author Jaroslav Bachorik */ public class BTraceDeque implements Deque, BTraceCollection, Cloneable { private final Deque delegate; public BTraceDeque(Deque delegate) { this.delegate = delegate; } @Override public synchronized String toString() { return delegate.toString(); } @Override public synchronized T[] toArray(T[] a) { return delegate.toArray(a); } @Override public synchronized Object[] toArray() { return delegate.toArray(); } @Override public synchronized boolean retainAll(Collection c) { return delegate.retainAll(c); } @Override public synchronized boolean removeAll(Collection c) { return delegate.removeAll(c); } @Override public synchronized boolean isEmpty() { return delegate.isEmpty(); } @Override public synchronized boolean containsAll(Collection c) { return delegate.containsAll(c); } @Override public synchronized void clear() { delegate.clear(); } @Override public synchronized boolean addAll(Collection c) { return delegate.addAll(c); } @Override public synchronized int size() { return delegate.size(); } @Override public synchronized boolean removeLastOccurrence(Object o) { return delegate.removeLastOccurrence(o); } @Override public synchronized V removeLast() { return delegate.removeLast(); } @Override public synchronized boolean removeFirstOccurrence(Object o) { return delegate.removeFirstOccurrence(o); } @Override public synchronized V removeFirst() { return delegate.removeFirst(); } @Override public synchronized boolean remove(Object o) { return delegate.remove(o); } @Override public synchronized V remove() { return delegate.remove(); } @Override public synchronized void push(V e) { delegate.push(e); } @Override public synchronized V pop() { return delegate.pop(); } @Override public synchronized V pollLast() { return delegate.pollLast(); } @Override public synchronized V pollFirst() { return delegate.pollFirst(); } @Override public synchronized V poll() { return delegate.poll(); } @Override public synchronized V peekLast() { return delegate.peekLast(); } @Override public synchronized V peekFirst() { return delegate.peekFirst(); } @Override public synchronized V peek() { return delegate.peek(); } @Override public synchronized boolean offerLast(V e) { return delegate.offerLast(e); } @Override public synchronized boolean offerFirst(V e) { return delegate.offerFirst(e); } @Override public synchronized boolean offer(V e) { return delegate.offer(e); } @Override public synchronized Iterator iterator() { return delegate.iterator(); } @Override public synchronized V getLast() { return delegate.getLast(); } @Override public synchronized V getFirst() { return delegate.getFirst(); } @Override public synchronized V element() { return delegate.element(); } @Override public synchronized Iterator descendingIterator() { return delegate.descendingIterator(); } @Override public synchronized boolean contains(Object o) { return delegate.contains(o); } @Override public synchronized void addLast(V e) { delegate.addLast(e); } @Override public synchronized void addFirst(V e) { delegate.addFirst(e); } @Override public synchronized boolean add(V e) { return delegate.add(e); } @Override public synchronized int hashCode() { return delegate.hashCode(); } @Override public synchronized boolean equals(Object obj) { return delegate.equals(obj); } @SuppressWarnings({"RedundantThrows", "MethodDoesntCallSuperMethod"}) @Override protected Object clone() throws CloneNotSupportedException { return new BTraceDeque<>(new ArrayDeque<>()); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceMap.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.types; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; /** * Instances of this class are used to store aggregate tracing data in BTrace. * * @author A. Sundararajan */ public final class BTraceMap implements Map, Cloneable { // private int numItems; private final Map m; private final boolean isWeak; private transient Set keySet = null; private transient Set> entrySet = null; private transient Collection values = null; public BTraceMap(Map m) { if (m == null) { throw new NullPointerException(); } this.m = m; isWeak = (m instanceof WeakHashMap); } @Override public synchronized int size() { return m.size(); } @Override public synchronized boolean isEmpty() { return m.isEmpty(); } @Override public synchronized boolean containsKey(Object key) { return m.containsKey(key); } @Override public synchronized boolean containsValue(Object value) { return m.containsValue(value); } @Override public synchronized V get(Object key) { return m.get(key); } @Override public synchronized V put(K key, V value) { return m.put(key, value); } @Override public synchronized V remove(Object key) { return m.remove(key); } @Override public synchronized void putAll(Map map) { m.putAll(map); } @Override public synchronized void clear() { m.clear(); } @Override public synchronized Set keySet() { if (keySet == null) { keySet = m.keySet(); } return keySet; } @Override public synchronized Set> entrySet() { if (entrySet == null) { entrySet = m.entrySet(); } return entrySet; } @Override public synchronized Collection values() { if (values == null) { values = m.values(); } return values; } @Override public synchronized boolean equals(Object o) { return m.equals(o); } @Override public synchronized int hashCode() { return m.hashCode(); } @Override public synchronized String toString() { return m.toString(); } @SuppressWarnings({"RedundantThrows", "MethodDoesntCallSuperMethod"}) @Override protected Object clone() throws CloneNotSupportedException { return new BTraceMap<>(isWeak ? new WeakHashMap<>() : new HashMap<>()); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/runtime/BTraceRuntimeAccess.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.runtime; import org.openjdk.btrace.core.BTraceRuntimeBridge; import org.openjdk.btrace.core.extensions.ExtensionContext; import org.openjdk.btrace.core.handlers.ErrorHandler; import org.openjdk.btrace.core.handlers.EventHandler; import org.openjdk.btrace.core.handlers.ExitHandler; import org.openjdk.btrace.core.handlers.LowMemoryHandler; import org.openjdk.btrace.core.handlers.TimerHandler; /** * Bootstrap-visible runtime access shim. */ public final class BTraceRuntimeAccess { private static volatile Delegate delegate; // for testing purposes; needs to be non-final private static volatile boolean uniqueClientClassNames = true; private BTraceRuntimeAccess() {} public static void install(Delegate delegate) { BTraceRuntimeAccess.delegate = delegate; } public static boolean isUniqueClientClassNames() { return uniqueClientClassNames; } public static boolean enter(BTraceRuntimeBridge currentRt) { Delegate current = delegate; return current != null && current.enter(currentRt); } public static void leave() { Delegate current = delegate; if (current != null) { current.leave(); } } public static BTraceRuntimeBridge forClass( Class cl, TimerHandler[] tHandlers, EventHandler[] evHandlers, ErrorHandler[] errHandlers, ExitHandler[] eHandlers, LowMemoryHandler[] lmHandlers) { Delegate current = delegate; return current != null ? current.forClass(cl, tHandlers, evHandlers, errHandlers, eHandlers, lmHandlers) : null; } public static ThreadLocal newThreadLocal(Object initValue) { Delegate current = delegate; return current != null ? current.newThreadLocal(initValue) : ThreadLocal.withInitial(() -> initValue); } public static String getClientName(String forClassName) { Delegate current = delegate; return current != null ? current.getClientName(forClassName) : forClassName; } public static ExtensionContext currentContext() { Delegate current = delegate; return current != null ? current.currentContext() : null; } public interface Delegate { boolean enter(BTraceRuntimeBridge currentRt); void leave(); BTraceRuntimeBridge forClass( Class cl, TimerHandler[] tHandlers, EventHandler[] evHandlers, ErrorHandler[] errHandlers, ExitHandler[] eHandlers, LowMemoryHandler[] lmHandlers); ThreadLocal newThreadLocal(Object initValue); String getClientName(String forClassName); ExtensionContext currentContext(); } } ================================================ FILE: btrace-core/src/main/java/org/openjdk/btrace/runtime/LinkingFlag.java ================================================ package org.openjdk.btrace.runtime; public final class LinkingFlag { private static final ThreadLocal linking = new ThreadLocal<>(); public static int guardLinking() { Integer current = linking.get(); current = current == null ? 0 : current; linking.set(current + 1); return current; } public static int get() { Integer current = linking.get(); return current == null ? 0 : current; } public static void reset() { Integer current = linking.get(); current = current == null ? 0 : current; linking.set(current > 0 ? current - 1 : 0); } } ================================================ FILE: btrace-core/src/main/resources/org/openjdk/btrace/core/annotations/jaxb.index ================================================ Kind Where ================================================ FILE: btrace-core/src/main/resources/org/openjdk/btrace/core/messages.properties ================================================ # BTrace "compile time" and runtime error messages. return.type.should.be.void=btrace probe methods must return void btrace.program.should.be.class=btrace program must be a class (not interface or enum) no.complex.unsafe.value=@BTrace.trusted|unsafe value should be only a plain boolean not.a.btrace.program=@BTrace annotation is missing no.outer.class=btrace class should not be a local class no.asserts=asserts are not allowed no.catch=catching exception is not allowed no.do.while=do..while loops are not allowed no.enhanced.for=enhanced for statements are not allowed no.loops=loops (backward jumps) are not allowed no.for.loop=for loops are not allowed no.array.creation=array creation is not allowed no.new.object=object creation is not allowed; construct via a service-provided builder or factory and pass the result to the service no.synchronized.blocks=synchronized blocks are not allowed no.synchronized.methods=probe action methods should not be synchronized no.throw=throwing exception is not allowed no.try=try .. catch .. finally blocks are not allowed no.while.loop=while loops are not allowed no.other="other" statement(s) are not allowed execution.loop.danger=execution of btrace program may lead to endless loop which is not allowed method.should.be.public=btrace methods should be public class.should.be.public=btrace class should be public no.static.method=static methods are not allowed (using BTrace short syntax) no.instance.method=instance methods are not allowed no.method.calls=method calls are not allowed - only BTraceUtils and calls on injected services or objects returned from services are allowed no.assignment=side-effects to probed program are not allowed no.nested.class=nested and inner classes are not allowed no.local.class=local classes are not allowed no.static.variables=static variables are not allowed (using BTrace short syntax) no.instance.variables=instance variables are not allowed object.superclass.required=btrace class should extend java.lang.Object no.interface.implementation=btrace class can not implement interfaces no.class.literals=class literals are not allowed self.desc.invalid=@Self annotation applicable only for Kind.ENTRY and Kind.RETURN probemethod.desc.invalid=@ProbeMethod annotation applicable only for Kind.ENTRY, Kind.RETURN and Kind.CALL probeclass.desc.invalid=@ProbeClassName annotation applicable only for Kind.ENTRY, Kind.RETURN and Kind.CALL return.desc.invalid=@Return annotation applicable only for Kind.RETURN duration.desc.invalid=@Duration annotation applicable only for Kind.RETURN and Kind.ERROR target-method.desc.invalid=@TargetMethodOrField annotation applicable only for Kind.CALL, Kind.FIELD_GET, Kind.FIELD_SET, Kind.ARRAY_GET and Kind.ARRAY_SET target-instance.desc.invalid=@TargetInstance annotation applicable only for Kind.CALL, Kind.FIELD_GET, Kind.FIELD_SET, Kind.ARRAY_GET, Kind.ARRAY_SET, Kind.INSTANCEOF, Kind.CHECKCAST, Kind.ERROR, Kind.THROW, Kind.CATCH, Kind.SYNC_ENTRY or Kind.SYNC_EXIT onexit.invalid=@OnExit annotation applicable only to methods with signature (int)void onerror.invalid=@OnError annotation applicable only to methods with signature (java.lang.Throwable)void multiple.param.annotation=Multiple parameters annotated by the same annotation are not allowed sampler.invalid.location=@Sampled annotation supported only for @OnMethod annotated classes with Kind of [ENTRY, RETURN, ERROR, CALL] missing.injected=Service fields must be annotated by @Injected injected.no.initializer=Injected fields must not use initializer invalid.injected.service=Injected type is not declared as an extension service missing.event=Event template fields must be annotated by either @Event or @PeriodicEvent event.nonpublic.handler=Periodic event handler must be public event.invalid.handler=Invalid periodic event handler specified - the handler method must take exactly one parameter of JfrEvent type agent.no.instance.variables=instance variables are not allowed agent.unsafe.not.allowed=Trusted mode, requested by the script, not allowed jfr.event.invalid.field=Invalid JFR event field name permission.missing=extension requires permission that is not granted; grant it via the agent (--grant=...) or extension configuration remote.commands.help=\ BTrace remote commands:\n \ - event : Send an event with an optional name\n \ - exit : Terminate the BTrace probe # usage messages btracec.usage=\ Usage: btracec \n\ where possible options include:\n \ -classpath Specify where to find user class files and annotation processors\n \ -cp Specify where to find user class files and annotation processors\n \ -I Specify where to find include files\n \ -d Specify where to place generated class files\n \ -nopack Do not produce packed probes. Useful when targeting pre 1.3.10 versions.\n \ -packext File extension for script packs (default '.class'). Valid only if '-nopack' is not specified.\n \ -trusted Enable trusted script (eg. no checks) btrace.usage=\ Usage: btrace \n\ where possible options include:\n \ --version Show the version\n \ -v Run in verbose mode\n \ -l List all locally attachable JVMs\n \ -lp List active probes in the given JVM\n \ \t\t\tExpects PID or app name as the follow-up argument\n \ \t\t\tAll other options are discarded\n \ -le List failed extensions in the given JVM\n \ \t\t\tExpects PID or app name as the follow-up argument\n \ \t\t\tAll other options are discarded\n \ -r Reconnect to an active disconnected probe\n \ \t\t\tExpects PID or app name as the follow-up argument\n \ \t\t\tAll other options are discarded\n \ -r help Show help on the remote commands\n \ -o The path to store the probe output (will disable showing the output in console)\n \ -u Run in trusted mode\n \ -d Dump the instrumented classes to the specified path\n \ -pd The search path for the probe XML descriptors\n \ -classpath Specify where to find user class files and annotation processors\n \ -cp Specify where to find user class files and annotation processors\n \ -I Specify where to find include files\n \ -p Specify port to which the btrace agent listens for clients\n \ -statsd Specify the statsd server, if any\n \ -n Execute a BTrace oneliner expression\n \ --oneliner Example: -n 'javax.swing.*::* @entry { print method }'\n \ \t\t\tSupports @entry, @return, @error locations and\n \ \t\t\tprint, count, time, stack actions with filters\n \ --agent-jar Specify path to btrace-agent.jar (overrides auto-discovery)\n \ --boot-jar Specify path to btrace-boot.jar (overrides auto-discovery)\n \ --extract-agent [dir] Extract embedded agent JARs to directory (default: current dir)\n \ --grant Grant specific permissions (comma-separated)\n \ \t\t\tExample: --grant=NETWORK,THREADS\n \ --deny Deny specific permissions (comma-separated)\n \ --grantAll Grant all permissions (use with caution)\n \ -x Run unattended\n \ \t\t\tDeploy the given probe and disconnect\n\n \ Permissions: MESSAGING, AGGREGATION, JFR_EVENTS, PROFILING (default),\n \ FILE_READ, SYSTEM_PROPS, THREAD_INFO, MEMORY_INFO (standard),\n \ FILE_WRITE, NETWORK, THREADS, NATIVE, EXEC, REFLECTION,\n \ CLASSLOADER, UNLIMITED_MEMORY (privileged) btrace.agent.usage=\ Usage: java -javaagent:java-agent.jar=
\n\ where arguments is comma separated name=value pairs. Argument names include:\n \ bootClassPath boot classpath to be used\n \ systemClassPath system classpath to be used\n \ debug boolean flag to specify debug mode\n \ trusted boolean flag to enable trusted mode\n \ dumpClasses boolean flag to specify whether to dump .classes for instrumented classes\n \ dumpDir directory where instrumented .class files are saved\n \ help print this help message\n \ noServer boolean flag to specify whether to start btrace server or not\n \ port btrace agent server port\n \ statsd statsd server, if any (format )\n \ allowExtensions comma-separated list of extension IDs explicitly allowed to link implementations\n \ denyExtensions comma-separated list of extension IDs explicitly denied from linking implementations\n \ allowPrivileged allow all privileged extensions to link implementations (true/false)\n \ probeDescPath directories where @OnProbe mapping descriptor XML files are searched\n \ stdout redirect the btrace output to stdout instead of writing it to an arbitrary file (true/false)\n \ scriptdir the path to a directory containing scripts to be run at the agent startup\n \ scriptOutputFile the path to a file the btrace agent will store its output\n \ grant comma-separated list of permissions to grant (e.g. grant=NETWORK,THREADS)\n \ deny comma-separated list of permissions to deny (e.g. deny=EXEC,NATIVE)\n \ grantAll grant all permissions (true/false) - use with caution\n \ script comma separated list of compiled tracing scripts to be run at the agent startup; *MUST* be the last argument in the list\n\n \ Permissions:\n \ Default (always granted): MESSAGING, AGGREGATION, JFR_EVENTS, PROFILING\n \ Standard (granted unless denied): FILE_READ, SYSTEM_PROPS, THREAD_INFO, MEMORY_INFO\n \ Privileged (require explicit grant): FILE_WRITE, NETWORK, THREADS, NATIVE, EXEC,\n \ REFLECTION, CLASSLOADER, UNLIMITED_MEMORY btrace.version=BTrace v.@btrace.version@ (@hash@) ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/CircularBufferTest.java ================================================ package org.openjdk.btrace.core; import static org.junit.jupiter.api.Assertions.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; public class CircularBufferTest { @Test public void testAddOverflow() { CircularBuffer cb = new CircularBuffer<>(3); cb.add(1); cb.add(2); cb.add(3); cb.add(4); assertEquals(3, cb.getLength()); final List elements = new ArrayList<>(); cb.forEach( new Function() { @Override public Boolean apply(Integer value) { elements.add(value); return true; } }); assertEquals(0, cb.getLength()); assertEquals(Arrays.asList(2, 3, 4), elements); } @Test public void testAddOverflowSeveral() { CircularBuffer cb = new CircularBuffer<>(2); cb.add(1); cb.add(2); cb.add(3); cb.add(4); cb.add(5); cb.add(6); cb.add(7); assertEquals(2, cb.getLength()); final List elements = new ArrayList<>(); cb.forEach( new Function() { @Override public Boolean apply(Integer value) { elements.add(value); return true; } }); assertEquals(0, cb.getLength()); assertEquals(Arrays.asList(6, 7), elements); } @Test public void testAdd() { CircularBuffer cb = new CircularBuffer<>(2); cb.add(1); assertEquals(1, cb.getLength()); final List elements = new ArrayList<>(); cb.forEach( new Function() { @Override public Boolean apply(Integer value) { elements.add(value); return true; } }); assertEquals(0, cb.getLength()); assertEquals(Arrays.asList(1), elements); } @Test public void testAddFull() { CircularBuffer cb = new CircularBuffer<>(2); cb.add(1); cb.add(2); assertEquals(2, cb.getLength()); final List elements = new ArrayList<>(); cb.forEach( new Function() { @Override public Boolean apply(Integer value) { elements.add(value); return true; } }); assertEquals(0, cb.getLength()); assertEquals(Arrays.asList(1, 2), elements); } @Test public void testEmpty() { CircularBuffer cb = new CircularBuffer<>(2); final List elements = new ArrayList<>(); cb.forEach( new Function() { @Override public Boolean apply(Integer value) { elements.add(value); return true; } }); assertTrue(elements.isEmpty()); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/MethodIDTest.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. */ package org.openjdk.btrace.core; import static org.junit.jupiter.api.Assertions.*; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class MethodIDTest { @BeforeEach void resetState() throws Exception { // Reset the static fields for isolation between tests Field lastFld = MethodID.class.getDeclaredField("lastMethodId"); Field mapFld = MethodID.class.getDeclaredField("methodIds"); lastFld.setAccessible(true); mapFld.setAccessible(true); AtomicInteger last = (AtomicInteger) lastFld.get(null); @SuppressWarnings("unchecked") Map map = (Map) mapFld.get(null); last.set(1); map.clear(); } @Test void singleThreadedConsistency() { int id1 = MethodID.getMethodId("A#foo#()V"); int id2 = MethodID.getMethodId("A#foo#()V"); int id3 = MethodID.getMethodId("A#bar#()V"); assertEquals(id1, id2, "Same tag should return same ID"); assertNotEquals(id1, id3, "Different tags should return different IDs"); } @Test void concurrentGenerationHasNoDuplicates() throws Exception { int threads = 10; int idsPerThread = 1000; ExecutorService pool = Executors.newFixedThreadPool(threads); ConcurrentMap reverse = new ConcurrentHashMap<>(); CountDownLatch start = new CountDownLatch(1); CountDownLatch done = new CountDownLatch(threads); for (int t = 0; t < threads; t++) { final int idx = t; pool.submit( () -> { try { start.await(); for (int i = 0; i < idsPerThread; i++) { String tag = "C" + idx + "#m" + i + "#()V"; int id = MethodID.getMethodId(tag); String prev = reverse.putIfAbsent(id, tag); if (prev != null && !prev.equals(tag)) { fail("Duplicate ID assigned to different tags: " + id + " => " + prev + " vs " + tag); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); fail(e); } finally { done.countDown(); } }); } start.countDown(); assertTrue(done.await(30, TimeUnit.SECONDS), "Tasks should complete timely"); pool.shutdownNow(); // Ensure expected cardinality assertEquals(threads * idsPerThread, reverse.size(), "Every tag should map to a unique ID"); // Re-check stability: calling again returns same id Set secondPass = new HashSet<>(); for (Map.Entry e : reverse.entrySet()) { int id = MethodID.getMethodId(e.getValue()); assertTrue(secondPass.add(id), "IDs should still be unique on second pass"); } assertEquals(reverse.size(), secondPass.size()); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/PrefixMapTest.java ================================================ package org.openjdk.btrace.core; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class PrefixMapTest { @Test void addAndContainsWorkWithDuplicates() { PrefixMap pm = new PrefixMap(); pm.add("abc"); pm.add("abc"); // duplicate pm.add("abcd"); assertTrue(pm.contains("abc")); assertTrue(pm.contains("abcdef")); assertTrue(pm.contains("abcd")); assertFalse(pm.contains("ab")); assertFalse(pm.contains("abX")); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/ReflectiveFieldAccessTest.java ================================================ package org.openjdk.btrace.core; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class ReflectiveFieldAccessTest { @Test public void getIntTest() { D a = new D(); RuntimeException exception = assertThrows(RuntimeException.class, () -> BTraceUtils.Reflective.getInt("notExist", a)); assertTrue(exception.getMessage().contains("notExist")); } static class A { int a; } static class B extends A {} static class C extends A {} static class D extends C {} } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/InstrumentCommandTest.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.openjdk.btrace.core.ArgsMap; /** * Tests for InstrumentCommand defensive copying to prevent external modification of bytecode and * arguments. */ class InstrumentCommandTest { @Test void testConstructorWithArgsMapMakesDefensiveCopy() { byte[] bytecode = {0x01, 0x02, 0x03}; ArgsMap args = new ArgsMap(); args.put("key1", "value1"); InstrumentCommand cmd = new InstrumentCommand(bytecode, args); // Modify the original bytecode array bytecode[0] = (byte) 0xFF; // Modify the original ArgsMap args.put("key2", "value2"); // Verify command's internal state was not affected byte[] retrievedCode = cmd.getCode(); assertEquals(0x01, retrievedCode[0], "Bytecode should not be affected by external modification"); ArgsMap retrievedArgs = cmd.getArguments(); assertNull(retrievedArgs.get("key2"), "Args should not be affected by external modification"); assertEquals("value1", retrievedArgs.get("key1")); } @Test void testConstructorWithStringArrayArgs() { byte[] bytecode = {0x01, 0x02, 0x03}; String[] args = {"key1=value1", "key2=value2"}; InstrumentCommand cmd = new InstrumentCommand(bytecode, args); // Modify original bytecode bytecode[0] = (byte) 0xFF; // Verify bytecode is protected assertEquals(0x01, cmd.getCode()[0]); // Verify args were parsed correctly assertEquals("value1", cmd.getArguments().get("key1")); assertEquals("value2", cmd.getArguments().get("key2")); } @Test void testConstructorWithMapArgs() { byte[] bytecode = {0x01, 0x02, 0x03}; Map argsMap = new HashMap<>(); argsMap.put("key1", "value1"); InstrumentCommand cmd = new InstrumentCommand(bytecode, argsMap); // Modify original bytecode and map bytecode[0] = (byte) 0xFF; argsMap.put("key2", "value2"); // Verify command is protected assertEquals(0x01, cmd.getCode()[0]); assertNull(cmd.getArguments().get("key2")); } @Test void testGetCodeReturnsDefensiveCopy() { byte[] bytecode = {0x01, 0x02, 0x03}; InstrumentCommand cmd = new InstrumentCommand(bytecode, new ArgsMap()); byte[] retrieved1 = cmd.getCode(); byte[] retrieved2 = cmd.getCode(); // Modify the returned array retrieved1[0] = (byte) 0xFF; // Verify original is unchanged assertEquals(0x01, cmd.getCode()[0], "getCode() should return a defensive copy"); // Verify each call returns a new copy assertNotSame(retrieved1, retrieved2, "getCode() should return new copy each time"); assertEquals(0x01, retrieved2[0], "Second call should return unmodified bytecode"); } @Test void testNullBytecode() { InstrumentCommand cmd = new InstrumentCommand(null, new ArgsMap()); assertNull(cmd.getCode(), "Null bytecode should remain null"); } @Test void testNullArgs() { byte[] bytecode = {0x01, 0x02, 0x03}; InstrumentCommand cmd = new InstrumentCommand(bytecode, (ArgsMap) null); assertNull(cmd.getArguments(), "Null args should remain null"); } @Test void testEmptyBytecode() { byte[] bytecode = {}; InstrumentCommand cmd = new InstrumentCommand(bytecode, new ArgsMap()); assertNotNull(cmd.getCode()); assertEquals(0, cmd.getCode().length); } @Test void testEmptyArgs() { byte[] bytecode = {0x01, 0x02, 0x03}; ArgsMap args = new ArgsMap(); InstrumentCommand cmd = new InstrumentCommand(bytecode, args); assertNotNull(cmd.getArguments()); assertEquals(0, cmd.getArguments().size()); } @Test void testLargeBytecodeArray() { byte[] bytecode = new byte[1024 * 1024]; // 1MB for (int i = 0; i < bytecode.length; i++) { bytecode[i] = (byte) (i % 256); } InstrumentCommand cmd = new InstrumentCommand(bytecode, new ArgsMap()); // Modify original bytecode[1000] = (byte) 0xFF; // Verify copy is independent assertNotEquals((byte) 0xFF, cmd.getCode()[1000]); } @Test void testMultipleArguments() { byte[] bytecode = {0x01, 0x02, 0x03}; ArgsMap args = new ArgsMap(); args.put("arg1", "value1"); args.put("arg2", "value2"); args.put("arg3", "value3"); InstrumentCommand cmd = new InstrumentCommand(bytecode, args); // Modify original args.put("arg4", "value4"); args.put("arg1", "modified"); // Verify command's args are unchanged ArgsMap cmdArgs = cmd.getArguments(); assertEquals(3, cmdArgs.size()); assertEquals("value1", cmdArgs.get("arg1")); assertEquals("value2", cmdArgs.get("arg2")); assertEquals("value3", cmdArgs.get("arg3")); assertNull(cmdArgs.get("arg4")); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/JavaSerializationProtocolLeakTest.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. */ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; class JavaSerializationProtocolLeakTest { static class FailingInputStream extends InputStream { @Override public int read() throws IOException { throw new IOException("boom"); } } static class CloseTrackingOutputStream extends OutputStream { final AtomicBoolean closed = new AtomicBoolean(false); @Override public void write(int b) throws IOException { // accept anything } @Override public void close() throws IOException { closed.set(true); super.close(); } } @Test void constructorClosesOutputStreamOnInputInitFailure() { InputStream failingIn = new FailingInputStream(); CloseTrackingOutputStream trackingOut = new CloseTrackingOutputStream(); IOException ex = assertThrows(IOException.class, () -> new JavaSerializationProtocol(failingIn, trackingOut)); assertTrue(trackingOut.closed.get(), "Output stream should be closed when input init fails"); assertEquals("boom", ex.getMessage()); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/ListProbesCommandConcurrencyTest.java ================================================ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class ListProbesCommandConcurrencyTest { @Test void concurrentSetAndIterateDoesNotThrow() throws Exception { ListProbesCommand cmd = new ListProbesCommand(); CountDownLatch start = new CountDownLatch(1); CountDownLatch done = new CountDownLatch(2); Runnable writer = () -> { try { start.await(); for (int r = 0; r < 500; r++) { int size = (r % 10) + 1; List list = new ArrayList<>(size); for (int i = 0; i < size; i++) { list.add("probe-" + r + '-' + i); } cmd.setProbes(list); } } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } finally { done.countDown(); } }; Runnable reader = () -> { try { start.await(); for (int r = 0; r < 500; r++) { // print() StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); assertDoesNotThrow(() -> cmd.print(pw)); // write() assertDoesNotThrow( () -> { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { cmd.write(oos); } }); } } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } finally { done.countDown(); } }; new Thread(writer).start(); new Thread(reader).start(); start.countDown(); // Ensure both loops finished done.await(10, TimeUnit.SECONDS); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/NullSafetyTest.java ================================================ /* * Tests for null safety in command print() methods to prevent NullPointerExceptions. */ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; class NullSafetyTest { @Test void testErrorCommandPrintWithNullCause() { ErrorCommand cmd = new ErrorCommand(null); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); assertDoesNotThrow(() -> cmd.print(pw)); String output = sw.toString(); assertTrue(output.contains("! ERROR")); assertTrue(output.contains("No exception information available")); } @Test void testGridDataCommandWithNullRow() { List data = new ArrayList<>(); data.add(new Object[] {"Row1", 100}); data.add(null); data.add(new Object[] {"Row3", 300}); GridDataCommand cmd = new GridDataCommand("TestGrid", data); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); assertDoesNotThrow(() -> cmd.print(pw)); String output = sw.toString(); assertTrue(output.contains("Row1")); assertTrue(output.contains("Row3")); } @Test void testMessageCommandWithNullMessage() { MessageCommand cmd = new MessageCommand((String) null); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); assertDoesNotThrow(() -> cmd.print(pw)); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/ProtocolConfigTest.java ================================================ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; /** Tests for ProtocolConfig. */ public class ProtocolConfigTest { @AfterEach public void clearSystemProperties() { System.clearProperty("btrace.comm.protocol"); System.clearProperty("btrace.comm.autoNegotiate"); System.clearProperty("btrace.comm.forceVersion"); } @Test public void testDefaultConfig() { ProtocolConfig config = ProtocolConfig.getDefault(); assertEquals(ProtocolVersion.V2, config.getVersion()); assertTrue(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testBuilderDefaultValues() { ProtocolConfig config = ProtocolConfig.builder().build(); assertEquals(ProtocolVersion.V2, config.getVersion()); assertTrue(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testBuilderCustomVersion() { ProtocolConfig config = ProtocolConfig.builder().version(ProtocolVersion.V1).build(); assertEquals(ProtocolVersion.V1, config.getVersion()); assertTrue(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testBuilderDisableAutoNegotiate() { ProtocolConfig config = ProtocolConfig.builder().autoNegotiate(false).build(); assertEquals(ProtocolVersion.V2, config.getVersion()); assertFalse(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testBuilderForceVersion() { ProtocolConfig config = ProtocolConfig.builder() .version(ProtocolVersion.V1) .autoNegotiate(false) .forceVersion(true) .build(); assertEquals(ProtocolVersion.V1, config.getVersion()); assertFalse(config.isAutoNegotiate()); assertTrue(config.isForceVersion()); } @Test public void testBuilderInvalidConfiguration() { // Cannot have both forceVersion and autoNegotiate enabled assertThrows( IllegalArgumentException.class, () -> ProtocolConfig.builder().autoNegotiate(true).forceVersion(true).build()); } @Test public void testFromSystemPropertiesNoProperties() { ProtocolConfig config = ProtocolConfig.fromSystemProperties(); // Should return default config when no properties are set assertEquals(ProtocolVersion.V2, config.getVersion()); assertTrue(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testFromSystemPropertiesProtocolV1() { System.setProperty("btrace.comm.protocol", "v1"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V1, config.getVersion()); assertTrue(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testFromSystemPropertiesProtocolV2() { System.setProperty("btrace.comm.protocol", "v2"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V2, config.getVersion()); assertTrue(config.isAutoNegotiate()); } @Test public void testFromSystemPropertiesProtocolNumeric() { System.setProperty("btrace.comm.protocol", "1"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V1, config.getVersion()); System.setProperty("btrace.comm.protocol", "2"); config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V2, config.getVersion()); } @Test public void testFromSystemPropertiesProtocolUppercase() { System.setProperty("btrace.comm.protocol", "V1"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V1, config.getVersion()); System.setProperty("btrace.comm.protocol", "V2"); config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V2, config.getVersion()); } @Test public void testFromSystemPropertiesInvalidProtocol() { System.setProperty("btrace.comm.protocol", "invalid"); assertThrows(IllegalArgumentException.class, () -> ProtocolConfig.fromSystemProperties()); } @Test public void testFromSystemPropertiesAutoNegotiate() { System.setProperty("btrace.comm.autoNegotiate", "false"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertFalse(config.isAutoNegotiate()); } @Test public void testFromSystemPropertiesForceVersion() { System.setProperty("btrace.comm.protocol", "v1"); System.setProperty("btrace.comm.autoNegotiate", "false"); System.setProperty("btrace.comm.forceVersion", "true"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V1, config.getVersion()); assertFalse(config.isAutoNegotiate()); assertTrue(config.isForceVersion()); } @Test public void testFromSystemPropertiesAllProperties() { System.setProperty("btrace.comm.protocol", "v2"); System.setProperty("btrace.comm.autoNegotiate", "true"); System.setProperty("btrace.comm.forceVersion", "false"); ProtocolConfig config = ProtocolConfig.fromSystemProperties(); assertEquals(ProtocolVersion.V2, config.getVersion()); assertTrue(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testToString() { ProtocolConfig config = ProtocolConfig.getDefault(); String str = config.toString(); assertTrue(str.contains("version=")); assertTrue(str.contains("autoNegotiate=")); assertTrue(str.contains("forceVersion=")); } @Test public void testBuilderChaining() { ProtocolConfig config = ProtocolConfig.builder() .version(ProtocolVersion.V1) .autoNegotiate(false) .forceVersion(false) .build(); assertEquals(ProtocolVersion.V1, config.getVersion()); assertFalse(config.isAutoNegotiate()); assertFalse(config.isForceVersion()); } @Test public void testMultipleBuilds() { ProtocolConfig.Builder builder = ProtocolConfig.builder(); ProtocolConfig config1 = builder.version(ProtocolVersion.V1).build(); ProtocolConfig config2 = builder.version(ProtocolVersion.V2).build(); // Each build should create independent instances assertNotSame(config1, config2); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/ProtocolNegotiatorTest.java ================================================ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import org.junit.jupiter.api.Test; /** Tests for ProtocolNegotiator and ProtocolVersion. */ public class ProtocolNegotiatorTest { @Test public void testProtocolVersionV1() { assertEquals(1, ProtocolVersion.V1.getVersion()); assertFalse(ProtocolVersion.V1.hasMagicBytes()); assertEquals(0, ProtocolVersion.V1.getMagicBytes().length); } @Test public void testProtocolVersionV2() { assertEquals(2, ProtocolVersion.V2.getVersion()); assertTrue(ProtocolVersion.V2.hasMagicBytes()); byte[] magicBytes = ProtocolVersion.V2.getMagicBytes(); assertEquals(4, magicBytes.length); assertEquals(0x42, magicBytes[0]); // 'B' assertEquals(0x54, magicBytes[1]); // 'T' assertEquals(0x52, magicBytes[2]); // 'R' assertEquals(0x32, magicBytes[3]); // '2' } @Test public void testProtocolVersionFromVersion() { assertEquals(ProtocolVersion.V1, ProtocolVersion.fromVersion(1)); assertEquals(ProtocolVersion.V2, ProtocolVersion.fromVersion(2)); assertThrows(IllegalArgumentException.class, () -> ProtocolVersion.fromVersion(0)); assertThrows(IllegalArgumentException.class, () -> ProtocolVersion.fromVersion(3)); assertThrows(IllegalArgumentException.class, () -> ProtocolVersion.fromVersion(-1)); } @Test public void testProtocolVersionDetectFromPrefix() { // V2 magic bytes byte[] v2Prefix = {0x42, 0x54, 0x52, 0x32}; // "BTR2" assertEquals(ProtocolVersion.V2, ProtocolVersion.detectFromPrefix(v2Prefix)); // V2 with extra bytes byte[] v2PrefixExtra = {0x42, 0x54, 0x52, 0x32, 0x01, 0x02}; assertEquals(ProtocolVersion.V2, ProtocolVersion.detectFromPrefix(v2PrefixExtra)); // V1 (no magic bytes) byte[] v1Prefix = {(byte) 0xAC, (byte) 0xED, 0x00, 0x05}; // Java serialization header assertEquals(ProtocolVersion.V1, ProtocolVersion.detectFromPrefix(v1Prefix)); // Empty assertEquals(ProtocolVersion.V1, ProtocolVersion.detectFromPrefix(new byte[0])); assertEquals(ProtocolVersion.V1, ProtocolVersion.detectFromPrefix(null)); // Incomplete V2 magic byte[] incomplete = {0x42, 0x54, 0x52}; // Only "BTR" assertEquals(ProtocolVersion.V1, ProtocolVersion.detectFromPrefix(incomplete)); // Wrong magic bytes byte[] wrong = {0x42, 0x54, 0x52, 0x31}; // "BTR1" assertEquals(ProtocolVersion.V1, ProtocolVersion.detectFromPrefix(wrong)); } @Test public void testProtocolVersionGetDefault() { assertEquals(ProtocolVersion.V2, ProtocolVersion.getDefault()); } @Test public void testNegotiatorDefaultConstructor() { ProtocolNegotiator negotiator = new ProtocolNegotiator(); assertEquals(ProtocolVersion.V2, negotiator.getPreferredVersion()); } @Test public void testNegotiatorCustomVersion() { ProtocolNegotiator negotiator = new ProtocolNegotiator(ProtocolVersion.V1); assertEquals(ProtocolVersion.V1, negotiator.getPreferredVersion()); } @Test public void testNegotiateV2WithPushback() throws IOException { byte[] v2Data = {0x42, 0x54, 0x52, 0x32, 0x01, 0x02, 0x03}; // "BTR2" + data PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(v2Data), 4); ProtocolNegotiator negotiator = new ProtocolNegotiator(); ProtocolVersion detected = negotiator.negotiate(pis); assertEquals(ProtocolVersion.V2, detected); // After negotiation, stream should be positioned after magic bytes assertEquals(0x01, pis.read()); assertEquals(0x02, pis.read()); assertEquals(0x03, pis.read()); } @Test public void testNegotiateV1WithPushback() throws IOException { byte[] v1Data = {(byte) 0xAC, (byte) 0xED, 0x00, 0x05}; // Java serialization header PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(v1Data), 4); ProtocolNegotiator negotiator = new ProtocolNegotiator(); ProtocolVersion detected = negotiator.negotiate(pis); assertEquals(ProtocolVersion.V1, detected); // After negotiation, stream should be reset to beginning (bytes pushed back) assertEquals(0xAC, pis.read() & 0xFF); assertEquals(0xED, pis.read() & 0xFF); assertEquals(0x00, pis.read()); assertEquals(0x05, pis.read()); } @Test public void testNegotiateV2WithMark() throws IOException { byte[] v2Data = {0x42, 0x54, 0x52, 0x32, 0x01, 0x02, 0x03}; // "BTR2" + data ByteArrayInputStream bis = new ByteArrayInputStream(v2Data); ProtocolNegotiator negotiator = new ProtocolNegotiator(); ProtocolVersion detected = negotiator.negotiate(bis); assertEquals(ProtocolVersion.V2, detected); // After negotiation, stream should be positioned after magic bytes assertEquals(0x01, bis.read()); assertEquals(0x02, bis.read()); assertEquals(0x03, bis.read()); } @Test public void testNegotiateV1WithMark() throws IOException { byte[] v1Data = {(byte) 0xAC, (byte) 0xED, 0x00, 0x05}; // Java serialization header ByteArrayInputStream bis = new ByteArrayInputStream(v1Data); ProtocolNegotiator negotiator = new ProtocolNegotiator(); ProtocolVersion detected = negotiator.negotiate(bis); assertEquals(ProtocolVersion.V1, detected); // After negotiation, stream should be reset to beginning assertEquals(0xAC, bis.read() & 0xFF); assertEquals(0xED, bis.read() & 0xFF); assertEquals(0x00, bis.read()); assertEquals(0x05, bis.read()); } @Test public void testNegotiateEmptyStream() { byte[] emptyData = {}; PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(emptyData), 4); ProtocolNegotiator negotiator = new ProtocolNegotiator(); assertThrows(IOException.class, () -> negotiator.negotiate(pis)); } @Test public void testNegotiateUnsupportedStream() { // Create a stream that doesn't support mark/reset or pushback InputStream unsupported = new InputStream() { @Override public int read() throws IOException { return 0; } @Override public boolean markSupported() { return false; } }; ProtocolNegotiator negotiator = new ProtocolNegotiator(); assertThrows(IllegalArgumentException.class, () -> negotiator.negotiate(unsupported)); } @Test public void testCreateNegotiationStream() { ByteArrayInputStream bis = new ByteArrayInputStream(new byte[10]); PushbackInputStream pis = ProtocolNegotiator.createNegotiationStream(bis); assertNotNull(pis); assertTrue(pis instanceof PushbackInputStream); } @Test public void testCreateNegotiationStreamAlreadyPushback() { PushbackInputStream original = new PushbackInputStream(new ByteArrayInputStream(new byte[10])); PushbackInputStream result = ProtocolNegotiator.createNegotiationStream(original); assertSame(original, result); } @Test public void testNegotiateMultipleV2Connections() throws IOException { // Simulate multiple connections to ensure negotiation works repeatedly for (int i = 0; i < 5; i++) { byte[] v2Data = {0x42, 0x54, 0x52, 0x32, (byte) i}; PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(v2Data), 4); ProtocolNegotiator negotiator = new ProtocolNegotiator(); ProtocolVersion detected = negotiator.negotiate(pis); assertEquals(ProtocolVersion.V2, detected); assertEquals(i, pis.read()); // Verify data after magic bytes } } @Test public void testNegotiateMultipleV1Connections() throws IOException { // Simulate multiple connections to ensure negotiation works repeatedly for (int i = 0; i < 5; i++) { byte[] v1Data = {(byte) 0xAC, (byte) 0xED, 0x00, (byte) i}; PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(v1Data), 4); ProtocolNegotiator negotiator = new ProtocolNegotiator(); ProtocolVersion detected = negotiator.negotiate(pis); assertEquals(ProtocolVersion.V1, detected); assertEquals(0xAC, pis.read() & 0xFF); // Verify pushed back } } @Test public void testProtocolVersionToString() { assertTrue(ProtocolVersion.V1.toString().contains("version=1")); assertTrue(ProtocolVersion.V1.toString().contains("hasMagicBytes=false")); assertTrue(ProtocolVersion.V2.toString().contains("version=2")); assertTrue(ProtocolVersion.V2.toString().contains("hasMagicBytes=true")); } @Test public void testMagicBytesImmutable() { // Ensure that modifying returned magic bytes doesn't affect the enum byte[] magicBytes = ProtocolVersion.V2.getMagicBytes(); byte original = magicBytes[0]; magicBytes[0] = 0x00; // Try to modify byte[] magicBytes2 = ProtocolVersion.V2.getMagicBytes(); assertEquals(original, magicBytes2[0]); // Should still be original value } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/TypeSafetyTest.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; /** * Tests for type safety validation in Command deserialization. * These tests ensure that malformed or malicious data cannot cause * ClassCastException at runtime. */ class TypeSafetyTest { @Test void testNumberDataCommandValidNumber() throws Exception { NumberDataCommand cmd = new NumberDataCommand("test", 42); // Serialize and deserialize NumberDataCommand deserialized = roundTrip(cmd, NumberDataCommand.class); assertEquals("test", deserialized.name); assertEquals(42, deserialized.getValue()); } @Test void testNumberDataCommandNullValue() throws Exception { NumberDataCommand cmd = new NumberDataCommand("test", null); NumberDataCommand deserialized = roundTrip(cmd, NumberDataCommand.class); assertEquals("test", deserialized.name); assertNull(deserialized.getValue()); } @Test void testNumberDataCommandRejectsInvalidType() throws Exception { // Create a command and manually serialize with wrong type ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); // Write command header oos.writeByte(Command.NUMBER); // Write name oos.writeUTF("test"); // Write WRONG type (String instead of Number) oos.writeObject("not a number"); oos.flush(); // Try to deserialize - should fail during WireIO.read() ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); IOException thrown = assertThrows(IOException.class, () -> { WireIO.read(ois); }); assertTrue(thrown.getMessage().contains("Invalid data type")); } @Test void testErrorCommandValidThrowable() throws Exception { Throwable cause = new RuntimeException("test error"); ErrorCommand cmd = new ErrorCommand(cause); ErrorCommand deserialized = roundTrip(cmd, ErrorCommand.class); assertNotNull(deserialized.getCause()); assertEquals("test error", deserialized.getCause().getMessage()); } @Test void testErrorCommandNullCause() throws Exception { ErrorCommand cmd = new ErrorCommand(null); ErrorCommand deserialized = roundTrip(cmd, ErrorCommand.class); assertNull(deserialized.getCause()); } @Test void testErrorCommandRejectsInvalidType() throws Exception { // Create a command and manually serialize with wrong type ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); // Write command header oos.writeByte(Command.ERROR); // Write WRONG type (String instead of Throwable) oos.writeObject("not a throwable"); oos.flush(); // Try to deserialize ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); IOException thrown = assertThrows(IOException.class, () -> { WireIO.read(ois); }); assertTrue(thrown.getMessage().contains("Invalid data type")); } @Test void testSetSettingsCommandValidTypes() throws Exception { Map params = new HashMap<>(); params.put("string_param", "value"); params.put("int_param", 42); params.put("bool_param", true); params.put("long_param", 123L); params.put("double_param", 3.14); SetSettingsCommand cmd = new SetSettingsCommand(params); SetSettingsCommand deserialized = roundTrip(cmd, SetSettingsCommand.class); assertEquals(5, deserialized.getParams().size()); assertEquals("value", deserialized.getParams().get("string_param")); assertEquals(42, deserialized.getParams().get("int_param")); assertEquals(true, deserialized.getParams().get("bool_param")); assertEquals(123L, deserialized.getParams().get("long_param")); assertEquals(3.14, deserialized.getParams().get("double_param")); } @Test void testSetSettingsCommandRejectsInvalidType() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); // Write command header oos.writeByte(Command.SET_PARAMS); // Write params with invalid type (use a serializable but invalid type) oos.writeInt(1); // size oos.writeUTF("bad_param"); oos.writeObject(new java.util.ArrayList<>()); // Invalid type - ArrayList is serializable but not a valid settings type oos.flush(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); IOException thrown = assertThrows(IOException.class, () -> { WireIO.read(ois); }); assertTrue(thrown.getMessage().contains("Invalid settings value type")); } @Test void testSetSettingsCommandRejectsNegativeSize() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeByte(Command.SET_PARAMS); oos.writeInt(-1); // Negative size! oos.flush(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); IOException thrown = assertThrows(IOException.class, () -> { WireIO.read(ois); }); assertTrue(thrown.getMessage().contains("Invalid params size")); } @Test void testSetSettingsCommandRejectsExcessiveSize() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeByte(Command.SET_PARAMS); oos.writeInt(100000); // Excessive size! oos.flush(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); IOException thrown = assertThrows(IOException.class, () -> { WireIO.read(ois); }); assertTrue(thrown.getMessage().contains("Invalid params size")); } // Helper method to serialize and deserialize a command private T roundTrip(T cmd, Class clazz) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); WireIO.write(oos, cmd); oos.flush(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Command result = WireIO.read(ois); assertInstanceOf(clazz, result); return clazz.cast(result); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/WireProtocolSessionTest.java ================================================ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.PushbackInputStream; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.Test; class WireProtocolSessionTest { private interface SessionHandler { void handle(WireProtocol protocol) throws Exception; } @Test void testDisconnectThenListProbesAcrossConnections() throws Exception { String probeId = "probe-12345"; String probeEntry = probeId + " [OnMethodTest]"; runV2Session( protocol -> { Command cmd = protocol.read(); assertTrue(cmd instanceof DisconnectCommand); protocol.write(new DisconnectCommand(probeId)); protocol.flush(); }, protocol -> { protocol.write(new DisconnectCommand()); protocol.flush(); Command cmd = protocol.read(); assertTrue(cmd instanceof DisconnectCommand); assertEquals(probeId, ((DisconnectCommand) cmd).getProbeId()); }); runV2Session( protocol -> { Command cmd = protocol.read(); assertTrue(cmd instanceof ListProbesCommand); ListProbesCommand response = (ListProbesCommand) cmd; response.setProbes(Collections.singletonList(probeEntry)); protocol.write(response); protocol.flush(); }, protocol -> { protocol.write(new ListProbesCommand()); protocol.flush(); Command cmd = protocol.read(); assertTrue(cmd instanceof ListProbesCommand); List probes = ((ListProbesCommand) cmd).getProbes(); assertEquals(1, probes.size()); assertEquals(probeEntry, probes.get(0)); }); } private static void runV2Session(SessionHandler serverHandler, SessionHandler clientHandler) throws Exception { Duplex duplex = new Duplex(); ExecutorService executor = Executors.newSingleThreadExecutor(); Future serverFuture = executor.submit( () -> { try (WireProtocol protocol = createServerProtocol(duplex.serverIn, duplex.serverOut)) { serverHandler.handle(protocol); } catch (Exception e) { throw new RuntimeException(e); } }); try (WireProtocol protocol = createClientProtocol(duplex.clientIn, duplex.clientOut)) { clientHandler.handle(protocol); } try { serverFuture.get(5, TimeUnit.SECONDS); } catch (ExecutionException e) { throw unwrap(e); } catch (TimeoutException e) { throw new AssertionError("Server session did not complete in time", e); } finally { executor.shutdownNow(); } } private static WireProtocol createServerProtocol(InputStream in, OutputStream out) throws Exception { ProtocolNegotiator negotiator = new ProtocolNegotiator(ProtocolVersion.V2); PushbackInputStream input = ProtocolNegotiator.createNegotiationStream(in); ProtocolVersion negotiated = negotiator.negotiateAgent(input, out); assertEquals(ProtocolVersion.V2, negotiated); WireProtocol protocol = new BinaryWireProtocol(input, out); assertEquals(ProtocolVersion.V2, protocol.getVersion()); return protocol; } private static WireProtocol createClientProtocol(InputStream in, OutputStream out) throws Exception { ProtocolNegotiator negotiator = new ProtocolNegotiator(ProtocolVersion.V2); ProtocolVersion negotiated = negotiator.negotiateClient(in, out, ProtocolVersion.V2); assertEquals(ProtocolVersion.V2, negotiated); WireProtocol protocol = new BinaryWireProtocol(in, out); assertEquals(ProtocolVersion.V2, protocol.getVersion()); return protocol; } private static Exception unwrap(ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof Exception) { return (Exception) cause; } return new RuntimeException(cause); } private static final class Duplex { final InputStream clientIn; final OutputStream clientOut; final InputStream serverIn; final OutputStream serverOut; Duplex() throws Exception { PipedInputStream clientInput = new PipedInputStream(32 * 1024); PipedInputStream serverInput = new PipedInputStream(32 * 1024); this.clientOut = new PipedOutputStream(serverInput); this.serverOut = new PipedOutputStream(clientInput); this.clientIn = clientInput; this.serverIn = serverInput; } } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/WireProtocolTest.java ================================================ package org.openjdk.btrace.core.comm; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import org.junit.jupiter.api.Test; /** Tests for WireProtocol interface and implementations. */ public class WireProtocolTest { @Test public void testJavaSerializationProtocolBasic() throws Exception { // Use piped streams for proper ObjectStream initialization PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); // Create protocols WireProtocol writer = WireProtocol.create(ProtocolVersion.V1, pis, pos); // Write command in separate thread to avoid deadlock Thread writerThread = new Thread(() -> { try { Command cmd = new MessageCommand("test message"); writer.write(cmd); writer.flush(); } catch (IOException e) { throw new RuntimeException(e); } }); writerThread.start(); // Read command Command readCmd = writer.read(); assertEquals(Command.MESSAGE, readCmd.getType()); assertTrue(readCmd instanceof MessageCommand); assertEquals("test message", ((MessageCommand) readCmd).getMessage()); assertEquals(ProtocolVersion.V1, writer.getVersion()); writerThread.join(); } @Test public void testBinaryWireProtocolBasic() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Write with binary protocol WireProtocol writer = new BinaryWireProtocol(new ByteArrayInputStream(new byte[0]), baos); Command cmd = new MessageCommand("test message"); writer.write(cmd); writer.flush(); // Read with binary protocol ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); WireProtocol reader = new BinaryWireProtocol(bais, new ByteArrayOutputStream()); Command readCmd = reader.read(); assertEquals(Command.MESSAGE, readCmd.getType()); assertTrue(readCmd instanceof MessageCommand); assertEquals("test message", ((MessageCommand) readCmd).getMessage()); assertEquals(ProtocolVersion.V2, reader.getVersion()); } @Test public void testWireProtocolCreateV1() throws Exception { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); WireProtocol protocol = WireProtocol.create(ProtocolVersion.V1, pis, pos); assertTrue(protocol instanceof JavaSerializationProtocol); assertEquals(ProtocolVersion.V1, protocol.getVersion()); protocol.close(); } @Test public void testWireProtocolCreateV2() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); WireProtocol protocol = WireProtocol.create(ProtocolVersion.V2, new ByteArrayInputStream(new byte[0]), baos); assertTrue(protocol instanceof BinaryWireProtocol); assertEquals(ProtocolVersion.V2, protocol.getVersion()); } @Test public void testWireProtocolWithNegotiationV2() throws Exception { // Create stream with V2 magic bytes ByteArrayOutputStream setupStream = new ByteArrayOutputStream(); setupStream.write(new byte[] {0x42, 0x54, 0x52, 0x32}); // "BTR2" // Add some dummy data for the protocol to work with ByteArrayInputStream inputStream = new ByteArrayInputStream(setupStream.toByteArray()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); WireProtocol protocol = WireProtocol.createWithNegotiation(inputStream, outputStream); assertEquals(ProtocolVersion.V2, protocol.getVersion()); assertTrue(protocol instanceof BinaryWireProtocol); } @Test public void testWireProtocolWithNegotiationV1() throws Exception { // Create stream with Java serialization header ByteArrayOutputStream setupStream = new ByteArrayOutputStream(); setupStream.write(new byte[] {(byte) 0xAC, (byte) 0xED, 0x00, 0x05}); ByteArrayInputStream inputStream = new ByteArrayInputStream(setupStream.toByteArray()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); WireProtocol protocol = WireProtocol.createWithNegotiation(inputStream, outputStream); assertEquals(ProtocolVersion.V1, protocol.getVersion()); assertTrue(protocol instanceof JavaSerializationProtocol); } @Test public void testWireProtocolWithConfigForceV1() throws Exception { ProtocolConfig config = ProtocolConfig.builder() .version(ProtocolVersion.V1) .autoNegotiate(false) .forceVersion(true) .build(); PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); WireProtocol protocol = WireProtocol.createWithConfig(config, pis, pos); assertEquals(ProtocolVersion.V1, protocol.getVersion()); assertTrue(protocol instanceof JavaSerializationProtocol); protocol.close(); } @Test public void testWireProtocolWithConfigAutoNegotiate() throws Exception { ProtocolConfig config = ProtocolConfig.builder() .version(ProtocolVersion.V2) // Preferred version .autoNegotiate(true) .build(); // Stream with V2 magic bytes ByteArrayOutputStream setupStream = new ByteArrayOutputStream(); setupStream.write(new byte[] {0x42, 0x54, 0x52, 0x32}); ByteArrayInputStream inputStream = new ByteArrayInputStream(setupStream.toByteArray()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); WireProtocol protocol = WireProtocol.createWithConfig(config, inputStream, outputStream); assertEquals(ProtocolVersion.V2, protocol.getVersion()); } @Test public void testMultipleCommandsV1() throws Exception { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); // Single protocol for both read and write WireProtocol protocol = new JavaSerializationProtocol(pis, pos); // Write multiple commands in separate thread Thread writerThread = new Thread(() -> { try { protocol.write(new MessageCommand("msg1")); protocol.write(new MessageCommand("msg2")); protocol.write(new ExitCommand(0)); protocol.flush(); } catch (IOException e) { throw new RuntimeException(e); } }); writerThread.start(); // Read multiple commands Command cmd1 = protocol.read(); assertEquals("msg1", ((MessageCommand) cmd1).getMessage()); Command cmd2 = protocol.read(); assertEquals("msg2", ((MessageCommand) cmd2).getMessage()); Command cmd3 = protocol.read(); assertEquals(Command.EXIT, cmd3.getType()); writerThread.join(); protocol.close(); } @Test public void testMultipleCommandsV2() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); WireProtocol writer = new BinaryWireProtocol(new ByteArrayInputStream(new byte[0]), baos); // Write multiple commands writer.write(new MessageCommand("msg1")); writer.write(new MessageCommand("msg2")); writer.write(new ExitCommand(0)); writer.flush(); // Read multiple commands ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); WireProtocol reader = new BinaryWireProtocol(bais, new ByteArrayOutputStream()); Command cmd1 = reader.read(); assertEquals("msg1", ((MessageCommand) cmd1).getMessage()); Command cmd2 = reader.read(); assertEquals("msg2", ((MessageCommand) cmd2).getMessage()); Command cmd3 = reader.read(); assertEquals(Command.EXIT, cmd3.getType()); } @Test public void testCloseV1() throws Exception { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); WireProtocol protocol = new JavaSerializationProtocol(pis, pos); protocol.close(); assertThrows(IOException.class, () -> protocol.write(new MessageCommand("test"))); assertThrows(IOException.class, () -> protocol.read()); } @Test public void testCloseV2() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); WireProtocol protocol = new BinaryWireProtocol(new ByteArrayInputStream(new byte[0]), baos); protocol.close(); assertThrows(IOException.class, () -> protocol.write(new MessageCommand("test"))); assertThrows(IOException.class, () -> protocol.read()); } @Test public void testJavaSerializationProtocolReset() throws Exception { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); JavaSerializationProtocol protocol = new JavaSerializationProtocol(pis, pos); // Reset should not throw protocol.reset(); // Should still be able to write after reset (in separate thread to avoid deadlock) Thread writerThread = new Thread(() -> { try { protocol.write(new MessageCommand("test")); protocol.flush(); } catch (IOException e) { throw new RuntimeException(e); } }); writerThread.start(); writerThread.join(); protocol.close(); } @Test public void testJavaSerializationProtocolAccessors() throws Exception { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); JavaSerializationProtocol protocol = new JavaSerializationProtocol(pis, pos); assertNotNull(protocol.getObjectInputStream()); assertNotNull(protocol.getObjectOutputStream()); protocol.close(); } @Test public void testBinaryWireProtocolAccessors() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); BinaryWireProtocol protocol = new BinaryWireProtocol(bais, baos); assertSame(bais, protocol.getInputStream()); assertSame(baos, protocol.getOutputStream()); } @Test public void testV1V2Interoperability() throws Exception { // Test V2 - simpler with byte arrays ByteArrayOutputStream v2Stream = new ByteArrayOutputStream(); WireProtocol v2Writer = new BinaryWireProtocol(new ByteArrayInputStream(new byte[0]), v2Stream); v2Writer.write(new MessageCommand("v2 message")); v2Writer.flush(); // Read with V2 WireProtocol v2Reader = new BinaryWireProtocol( new ByteArrayInputStream(v2Stream.toByteArray()), new ByteArrayOutputStream()); Command v2Cmd = v2Reader.read(); assertEquals("v2 message", ((MessageCommand) v2Cmd).getMessage()); v2Reader.close(); // Test V1 with piped streams PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); WireProtocol v1Protocol = new JavaSerializationProtocol(pis, pos); // Write in separate thread Thread writerThread = new Thread(() -> { try { v1Protocol.write(new MessageCommand("v1 message")); v1Protocol.flush(); } catch (IOException e) { throw new RuntimeException(e); } }); writerThread.start(); // Read Command v1Cmd = v1Protocol.read(); assertEquals("v1 message", ((MessageCommand) v1Cmd).getMessage()); writerThread.join(); v1Protocol.close(); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/v2/BinaryProtocolEdgeCasesTest.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; /** * Edge case tests for the binary protocol. * Tests null values, empty collections, large payloads, boundary conditions, and malformed data. */ public class BinaryProtocolEdgeCasesTest { // ===== NULL AND EMPTY VALUE TESTS ===== @Test public void testNullString() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryProtocol.writeString(baos, null); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); String result = BinaryProtocol.readString(bais); assertNull(result); } @Test public void testEmptyString() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryProtocol.writeString(baos, ""); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); String result = BinaryProtocol.readString(bais); assertNotNull(result); assertEquals("", result); } @Test public void testNullByteArray() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryProtocol.writeByteArray(baos, null); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); byte[] result = BinaryProtocol.readByteArray(bais); assertNull(result); } @Test public void testEmptyByteArray() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryProtocol.writeByteArray(baos, new byte[0]); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); byte[] result = BinaryProtocol.readByteArray(bais); assertNotNull(result); assertEquals(0, result.length); } @Test public void testMessageCommandNullMessage() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryMessageCommand original = new BinaryMessageCommand(null, false); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; // Empty message should be preserved as empty string (not null) assertEquals("", msgCommand.getMessage()); } @Test public void testErrorCommandNullMessage() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryErrorCommand original = new BinaryErrorCommand("java.lang.RuntimeException", null, null); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryErrorCommand); BinaryErrorCommand errorCommand = (BinaryErrorCommand) readCommand; assertEquals("java.lang.RuntimeException", errorCommand.getExceptionClass()); assertNull(errorCommand.getMessage()); assertNull(errorCommand.getStackTrace()); } @Test public void testEventCommandNullEvent() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryEventCommand original = new BinaryEventCommand(null); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryEventCommand); BinaryEventCommand eventCommand = (BinaryEventCommand) readCommand; assertNull(eventCommand.getEvent()); } @Test public void testStringMapWithNullValues() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Map data = new HashMap<>(); data.put("key1", "value1"); data.put("key2", null); data.put("key3", ""); BinaryStringMapDataCommand original = new BinaryStringMapDataCommand("TestMap", data); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryStringMapDataCommand); BinaryStringMapDataCommand mapCommand = (BinaryStringMapDataCommand) readCommand; Map readData = mapCommand.getData(); assertEquals("value1", readData.get("key1")); assertNull(readData.get("key2")); assertEquals("", readData.get("key3")); } @Test public void testEmptyNumberMap() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Map data = new HashMap<>(); BinaryNumberMapDataCommand original = new BinaryNumberMapDataCommand("EmptyMap", data); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryNumberMapDataCommand); BinaryNumberMapDataCommand mapCommand = (BinaryNumberMapDataCommand) readCommand; assertTrue(mapCommand.getData().isEmpty()); } // ===== BOUNDARY CONDITION TESTS ===== @Test public void testVeryLargeMessage() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create a 10MB message StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10 * 1024 * 1024; i++) { sb.append('A'); } String largeMessage = sb.toString(); BinaryMessageCommand original = new BinaryMessageCommand(largeMessage, false); BinaryWireIO.write(baos, original); // Verify it was written assertTrue(baos.size() > 0); // Verify it can be read back ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(largeMessage.length(), msgCommand.getMessage().length()); assertEquals(largeMessage, msgCommand.getMessage()); } @Test public void testLargeBytecodeArray() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create a 1MB bytecode array byte[] largeCode = new byte[1024 * 1024]; for (int i = 0; i < largeCode.length; i++) { largeCode[i] = (byte) (i % 256); } Map args = new HashMap<>(); args.put("test", "value"); BinaryInstrumentCommand original = new BinaryInstrumentCommand(largeCode, args); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryInstrumentCommand); BinaryInstrumentCommand instrCommand = (BinaryInstrumentCommand) readCommand; assertArrayEquals(largeCode, instrCommand.getCode()); } @Test public void testMapWith1000Entries() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Map data = new HashMap<>(); for (int i = 0; i < 1000; i++) { data.put("key" + i, i); } BinaryNumberMapDataCommand original = new BinaryNumberMapDataCommand("LargeMap", data); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryNumberMapDataCommand); BinaryNumberMapDataCommand mapCommand = (BinaryNumberMapDataCommand) readCommand; assertEquals(1000, mapCommand.getData().size()); for (int i = 0; i < 1000; i++) { assertEquals(i, mapCommand.getData().get("key" + i).intValue()); } } @Test public void testGridDataWithManyRows() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); List columnNames = new ArrayList<>(); columnNames.add("Col1"); columnNames.add("Col2"); List data = new ArrayList<>(); for (int i = 0; i < 500; i++) { data.add(new Object[]{"Row" + i, i}); } BinaryGridDataCommand original = new BinaryGridDataCommand("LargeGrid", columnNames, data); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryGridDataCommand); BinaryGridDataCommand gridCommand = (BinaryGridDataCommand) readCommand; assertEquals(500, gridCommand.getData().size()); } // ===== UNICODE AND SPECIAL CHARACTER TESTS ===== @Test public void testUnicodeStrings() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test various Unicode characters String unicode = "Hello 世界 🌍 مرحبا Привет"; BinaryMessageCommand original = new BinaryMessageCommand(unicode, false); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(unicode, msgCommand.getMessage()); } @Test public void testEmojiInMessage() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); String emoji = "🚀 BTrace launched! 🎉🎊🎈"; BinaryMessageCommand original = new BinaryMessageCommand(emoji, false); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(emoji, msgCommand.getMessage()); } @Test public void testControlCharactersInString() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); String controlChars = "Line1\nLine2\tTabbed\rCarriageReturn\0Null"; BinaryProtocol.writeString(baos, controlChars); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); String result = BinaryProtocol.readString(bais); assertEquals(controlChars, result); } // ===== MALFORMED DATA TESTS ===== @Test public void testInvalidProtocolVersion() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Write invalid protocol version BinaryProtocol.writeByte(baos, (byte) 99); // Invalid version BinaryProtocol.writeByte(baos, BinaryCommand.MESSAGE); // Valid command type ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); // Should throw IOException due to version mismatch assertThrows(IOException.class, () -> { BinaryWireIO.read(bais); }); } @Test public void testInvalidCommandType() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Write valid protocol version but invalid command type BinaryProtocol.writeByte(baos, BinaryProtocol.VERSION); BinaryProtocol.writeByte(baos, (byte) 99); // Invalid command type ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); // Should throw MalformedCommandException due to unknown command type assertThrows(MalformedCommandException.class, () -> { BinaryWireIO.read(bais); }); } @Test public void testTruncatedStream() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Write only protocol version and command type, but not the data BinaryProtocol.writeByte(baos, BinaryProtocol.VERSION); BinaryProtocol.writeByte(baos, BinaryCommand.MESSAGE); // Missing message data... ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); // Should throw IOException due to unexpected end of stream assertThrows(IOException.class, () -> { BinaryWireIO.read(bais); }); } // ===== COMPRESSION EDGE CASES ===== @Test public void testCompressionThreshold() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create message exactly at threshold (1024 bytes) StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1024; i++) { sb.append('X'); } String thresholdMessage = sb.toString(); BinaryMessageCommand original = new BinaryMessageCommand(thresholdMessage, false); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(thresholdMessage, msgCommand.getMessage()); } @Test public void testCompressionJustAboveThreshold() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create message just above threshold (1025 bytes) StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1025; i++) { sb.append('Y'); } String aboveThresholdMessage = sb.toString(); BinaryMessageCommand original = new BinaryMessageCommand(aboveThresholdMessage, false); BinaryWireIO.write(baos, original); // Message should be compressed, verify it's smaller than original int wireSize = baos.size(); // Wire size should be less than original due to compression // (accounting for protocol overhead) assertTrue(wireSize < aboveThresholdMessage.length()); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(aboveThresholdMessage, msgCommand.getMessage()); } @Test public void testHighlyCompressibleMessage() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create highly compressible message (many repeated characters) String compressible = "AAAAAAAAAA".repeat(1000); // 10,000 'A's BinaryMessageCommand original = new BinaryMessageCommand(compressible, false); BinaryWireIO.write(baos, original); // Compressed size should be much smaller than original int wireSize = baos.size(); assertTrue(wireSize < compressible.length() / 5); // Should compress to less than 20% ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(compressible, msgCommand.getMessage()); } // ===== NUMERIC BOUNDARY TESTS ===== @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}) public void testIntegerBoundaries(int value) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryProtocol.writeInt(baos, value); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); int result = BinaryProtocol.readInt(bais); assertEquals(value, result); } @ParameterizedTest @ValueSource(longs = {Long.MIN_VALUE, -1L, 0L, 1L, Long.MAX_VALUE}) public void testLongBoundaries(long value) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryProtocol.writeLong(baos, value); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); long result = BinaryProtocol.readLong(bais); assertEquals(value, result); } @Test public void testSpecialFloatValues() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test special float values BinaryProtocol.writeFloat(baos, Float.NaN); BinaryProtocol.writeFloat(baos, Float.POSITIVE_INFINITY); BinaryProtocol.writeFloat(baos, Float.NEGATIVE_INFINITY); BinaryProtocol.writeFloat(baos, Float.MIN_VALUE); BinaryProtocol.writeFloat(baos, Float.MAX_VALUE); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); assertTrue(Float.isNaN(BinaryProtocol.readFloat(bais))); assertEquals(Float.POSITIVE_INFINITY, BinaryProtocol.readFloat(bais)); assertEquals(Float.NEGATIVE_INFINITY, BinaryProtocol.readFloat(bais)); assertEquals(Float.MIN_VALUE, BinaryProtocol.readFloat(bais)); assertEquals(Float.MAX_VALUE, BinaryProtocol.readFloat(bais)); } @Test public void testSpecialDoubleValues() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test special double values BinaryProtocol.writeDouble(baos, Double.NaN); BinaryProtocol.writeDouble(baos, Double.POSITIVE_INFINITY); BinaryProtocol.writeDouble(baos, Double.NEGATIVE_INFINITY); BinaryProtocol.writeDouble(baos, Double.MIN_VALUE); BinaryProtocol.writeDouble(baos, Double.MAX_VALUE); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); assertTrue(Double.isNaN(BinaryProtocol.readDouble(bais))); assertEquals(Double.POSITIVE_INFINITY, BinaryProtocol.readDouble(bais)); assertEquals(Double.NEGATIVE_INFINITY, BinaryProtocol.readDouble(bais)); assertEquals(Double.MIN_VALUE, BinaryProtocol.readDouble(bais)); assertEquals(Double.MAX_VALUE, BinaryProtocol.readDouble(bais)); } // ===== SETTINGS COMMAND EDGE CASES ===== @Test public void testSetSettingsCommandWithMixedTypes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Map settings = new HashMap<>(); settings.put("stringValue", "test"); settings.put("intValue", 42); settings.put("longValue", 9876543210L); settings.put("boolValue", true); settings.put("floatValue", 3.14f); settings.put("doubleValue", 2.71828); settings.put("nullValue", null); BinarySetSettingsCommand original = new BinarySetSettingsCommand(settings); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinarySetSettingsCommand); BinarySetSettingsCommand settingsCommand = (BinarySetSettingsCommand) readCommand; Map readSettings = settingsCommand.getParams(); assertEquals("test", readSettings.get("stringValue")); assertEquals(42, readSettings.get("intValue")); assertEquals(9876543210L, readSettings.get("longValue")); assertEquals(true, readSettings.get("boolValue")); assertEquals(3.14f, ((Number) readSettings.get("floatValue")).floatValue(), 0.001); assertEquals(2.71828, ((Number) readSettings.get("doubleValue")).doubleValue(), 0.00001); assertNull(readSettings.get("nullValue")); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/v2/BinaryProtocolPerformanceTest.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.InstrumentCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.openjdk.btrace.core.comm.WireIO; /** * Performance tests comparing the binary protocol to Java serialization. * This is not included in the regular test suite to avoid slowing down normal builds. */ public class BinaryProtocolPerformanceTest { private static final int ITERATIONS = 10000; private static final byte[] SAMPLE_CODE = new byte[1024]; // 1KB of code private static final String LARGE_MESSAGE = generateLargeMessage(10 * 1024); // 10KB message static { // Initialize sample code with random data for (int i = 0; i < SAMPLE_CODE.length; i++) { SAMPLE_CODE[i] = (byte) (Math.random() * 256); } } private static String generateLargeMessage(int size) { StringBuilder sb = new StringBuilder(size); for (int i = 0; i < size; i++) { sb.append((char) ('a' + (i % 26))); } return sb.toString(); } /** * This test compares the performance of binary protocol vs Java serialization * for the InstrumentCommand, which contains bytecode and arguments. */ @Test public void testInstrumentCommandPerformance() throws IOException, ClassNotFoundException { // Create test data Map args = new HashMap<>(); args.put("className", "com.example.TestClass"); args.put("methodName", "testMethod"); args.put("probeId", "12345"); // Binary protocol long binaryStartTime = System.nanoTime(); long binaryTotalSize = 0; for (int i = 0; i < ITERATIONS; i++) { // Serialize ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryCommand cmd = new BinaryInstrumentCommand(SAMPLE_CODE, args); BinaryWireIO.write(baos, cmd); byte[] serialized = baos.toByteArray(); binaryTotalSize += serialized.length; // Deserialize ByteArrayInputStream bais = new ByteArrayInputStream(serialized); BinaryCommand deserialized = BinaryWireIO.read(bais); } long binaryEndTime = System.nanoTime(); long binaryDuration = binaryEndTime - binaryStartTime; // Java serialization long javaStartTime = System.nanoTime(); long javaTotalSize = 0; for (int i = 0; i < ITERATIONS; i++) { // Serialize ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); Command cmd = new InstrumentCommand(SAMPLE_CODE, args); WireIO.write(oos, cmd); oos.close(); byte[] serialized = baos.toByteArray(); javaTotalSize += serialized.length; // Deserialize ByteArrayInputStream bais = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bais); Command deserialized = WireIO.read(ois); ois.close(); } long javaEndTime = System.nanoTime(); long javaDuration = javaEndTime - javaStartTime; // Print results System.out.println("InstrumentCommand Performance Test Results:"); System.out.println(" Binary Protocol:"); System.out.println(" Time: " + (binaryDuration / 1_000_000) + " ms"); System.out.println(" Avg Size: " + (binaryTotalSize / ITERATIONS) + " bytes"); System.out.println(" Java Serialization:"); System.out.println(" Time: " + (javaDuration / 1_000_000) + " ms"); System.out.println(" Avg Size: " + (javaTotalSize / ITERATIONS) + " bytes"); System.out.println(" Improvement:"); System.out.println(" Time: " + (javaDuration / (double)binaryDuration) + "x faster"); System.out.println(" Size: " + (javaTotalSize / (double)binaryTotalSize) + "x smaller"); } /** * This test compares the performance of binary protocol vs Java serialization * for the MessageCommand with compression. */ @Test public void testMessageCommandPerformance() throws IOException, ClassNotFoundException { // Binary protocol long binaryStartTime = System.nanoTime(); long binaryTotalSize = 0; for (int i = 0; i < ITERATIONS; i++) { // Serialize ByteArrayOutputStream baos = new ByteArrayOutputStream(); BinaryCommand cmd = new BinaryMessageCommand(LARGE_MESSAGE, true); BinaryWireIO.write(baos, cmd); byte[] serialized = baos.toByteArray(); binaryTotalSize += serialized.length; // Deserialize ByteArrayInputStream bais = new ByteArrayInputStream(serialized); BinaryCommand deserialized = BinaryWireIO.read(bais); } long binaryEndTime = System.nanoTime(); long binaryDuration = binaryEndTime - binaryStartTime; // Java serialization long javaStartTime = System.nanoTime(); long javaTotalSize = 0; for (int i = 0; i < ITERATIONS; i++) { // Serialize ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); Command cmd = new MessageCommand(LARGE_MESSAGE); WireIO.write(oos, cmd); oos.close(); byte[] serialized = baos.toByteArray(); javaTotalSize += serialized.length; // Deserialize ByteArrayInputStream bais = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bais); Command deserialized = WireIO.read(ois); ois.close(); } long javaEndTime = System.nanoTime(); long javaDuration = javaEndTime - javaStartTime; // Print results System.out.println("MessageCommand Performance Test Results:"); System.out.println(" Binary Protocol (with compression):"); System.out.println(" Time: " + (binaryDuration / 1_000_000) + " ms"); System.out.println(" Avg Size: " + (binaryTotalSize / ITERATIONS) + " bytes"); System.out.println(" Java Serialization:"); System.out.println(" Time: " + (javaDuration / 1_000_000) + " ms"); System.out.println(" Avg Size: " + (javaTotalSize / ITERATIONS) + " bytes"); System.out.println(" Improvement:"); System.out.println(" Time: " + (javaDuration / (double)binaryDuration) + "x faster"); System.out.println(" Size: " + (javaTotalSize / (double)binaryTotalSize) + "x smaller"); } } ================================================ FILE: btrace-core/src/test/java/org/openjdk/btrace/core/comm/v2/BinaryProtocolTest.java ================================================ package org.openjdk.btrace.core.comm.v2; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.openjdk.btrace.core.ArgsMap; /** * Tests for the binary protocol implementation. */ public class BinaryProtocolTest { @Test public void testBinaryProtocolBasicTypes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Write test data BinaryProtocol.writeByte(baos, (byte) 123); BinaryProtocol.writeInt(baos, 42); BinaryProtocol.writeLong(baos, 9876543210L); BinaryProtocol.writeFloat(baos, 3.14f); BinaryProtocol.writeDouble(baos, 2.71828); BinaryProtocol.writeBoolean(baos, true); BinaryProtocol.writeString(baos, "Hello, World!"); byte[] testArray = {1, 2, 3, 4, 5}; BinaryProtocol.writeByteArray(baos, testArray); // Read test data ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); assertEquals((byte) 123, BinaryProtocol.readByte(bais)); assertEquals(42, BinaryProtocol.readInt(bais)); assertEquals(9876543210L, BinaryProtocol.readLong(bais)); assertEquals(3.14f, BinaryProtocol.readFloat(bais)); assertEquals(2.71828, BinaryProtocol.readDouble(bais)); assertTrue(BinaryProtocol.readBoolean(bais)); assertEquals("Hello, World!", BinaryProtocol.readString(bais)); byte[] readArray = BinaryProtocol.readByteArray(bais); assertArrayEquals(testArray, readArray); } @Test public void testBinaryExitCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryExitCommand original = new BinaryExitCommand(42); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryExitCommand); BinaryExitCommand exitCommand = (BinaryExitCommand) readCommand; assertEquals(42, exitCommand.getExitCode()); assertEquals(BinaryCommand.EXIT, exitCommand.getType()); assertTrue(exitCommand.isUrgent()); } @Test public void testBinaryMessageCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryMessageCommand original = new BinaryMessageCommand("Test message", true); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals("Test message", msgCommand.getMessage()); assertEquals(BinaryCommand.MESSAGE, msgCommand.getType()); assertEquals(original.isUrgent(), msgCommand.isUrgent()); } @Test public void testBinaryMessageCommandWithCompression() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create a large message that will trigger compression StringBuilder sb = new StringBuilder(); for (int i = 0; i < 2000; i++) { sb.append("This is a test message that will be compressed. "); } String largeMessage = sb.toString(); // Create and write command BinaryMessageCommand original = new BinaryMessageCommand(largeMessage, true); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryMessageCommand); BinaryMessageCommand msgCommand = (BinaryMessageCommand) readCommand; assertEquals(largeMessage, msgCommand.getMessage()); } @Test public void testBinaryInstrumentCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create test data byte[] code = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Map args = new HashMap<>(); args.put("key1", "value1"); args.put("key2", "value2"); // Create and write command BinaryInstrumentCommand original = new BinaryInstrumentCommand(code, args); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryInstrumentCommand); BinaryInstrumentCommand instrCommand = (BinaryInstrumentCommand) readCommand; assertArrayEquals(code, instrCommand.getCode()); ArgsMap readArgs = instrCommand.getArguments(); assertEquals(2, readArgs.size()); assertEquals("value1", readArgs.get("key1")); assertEquals("value2", readArgs.get("key2")); } @Test public void testBinaryGridDataCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create test data List columnNames = new ArrayList<>(); columnNames.add("Column1"); columnNames.add("Column2"); columnNames.add("Column3"); List data = new ArrayList<>(); data.add(new Object[] { "Row1Col1", 123, 3.14 }); data.add(new Object[] { "Row2Col1", 456, 2.71 }); data.add(new Object[] { "Row3Col1", 789, "Row3Col3" }); // Create and write command BinaryGridDataCommand original = new BinaryGridDataCommand("TestGrid", columnNames, data); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryGridDataCommand); BinaryGridDataCommand gridCommand = (BinaryGridDataCommand) readCommand; assertEquals("TestGrid", gridCommand.getName()); List readColumnNames = gridCommand.getColumnNames(); assertEquals(3, readColumnNames.size()); assertEquals("Column1", readColumnNames.get(0)); assertEquals("Column2", readColumnNames.get(1)); assertEquals("Column3", readColumnNames.get(2)); List readData = gridCommand.getData(); assertEquals(3, readData.size()); assertEquals("Row1Col1", readData.get(0)[0]); assertEquals(123, readData.get(0)[1]); assertEquals(3.14, readData.get(0)[2]); assertEquals("Row2Col1", readData.get(1)[0]); assertEquals(456, readData.get(1)[1]); assertEquals(2.71, readData.get(1)[2]); assertEquals("Row3Col1", readData.get(2)[0]); assertEquals(789, readData.get(2)[1]); assertEquals("Row3Col3", readData.get(2)[2]); } @Test public void testCommandAdapter() throws IOException { // Test command conversion from binary to original and back BinaryExitCommand exitCmd = new BinaryExitCommand(42); org.openjdk.btrace.core.comm.Command originalCmd = CommandAdapter.toBtraceCommand(exitCmd); assertEquals(org.openjdk.btrace.core.comm.Command.EXIT, originalCmd.getType()); BinaryCommand convertedBack = CommandAdapter.toBinaryCommand(originalCmd); assertTrue(convertedBack instanceof BinaryExitCommand); assertEquals(42, ((BinaryExitCommand) convertedBack).getExitCode()); } // Tests for the 13 previously untested command types @Test public void testBinaryErrorCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryErrorCommand original = new BinaryErrorCommand( "java.lang.IllegalStateException", "Test error message", "stack-trace"); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryErrorCommand); BinaryErrorCommand errorCommand = (BinaryErrorCommand) readCommand; assertEquals("java.lang.IllegalStateException", errorCommand.getExceptionClass()); assertEquals("Test error message", errorCommand.getMessage()); assertEquals("stack-trace", errorCommand.getStackTrace()); assertEquals(BinaryCommand.ERROR, errorCommand.getType()); } @Test public void testBinaryErrorCommandNullMessage() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command with null message BinaryErrorCommand original = new BinaryErrorCommand("java.lang.RuntimeException", null, null); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryErrorCommand); BinaryErrorCommand errorCommand = (BinaryErrorCommand) readCommand; assertEquals("java.lang.RuntimeException", errorCommand.getExceptionClass()); assertEquals(null, errorCommand.getMessage()); assertEquals(null, errorCommand.getStackTrace()); } @Test public void testBinaryEventCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryEventCommand original = new BinaryEventCommand("test.event.fired"); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryEventCommand); BinaryEventCommand eventCommand = (BinaryEventCommand) readCommand; assertEquals("test.event.fired", eventCommand.getEvent()); assertEquals(BinaryCommand.EVENT, eventCommand.getType()); } @Test public void testBinaryRenameCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryRenameCommand original = new BinaryRenameCommand("NewProbeName"); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryRenameCommand); BinaryRenameCommand renameCommand = (BinaryRenameCommand) readCommand; assertEquals("NewProbeName", renameCommand.getNewName()); assertEquals(BinaryCommand.RENAME, renameCommand.getType()); } @Test public void testBinaryStatusCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test success status BinaryStatusCommand original = new BinaryStatusCommand(1, true); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryStatusCommand); BinaryStatusCommand statusCommand = (BinaryStatusCommand) readCommand; assertEquals(1, statusCommand.getFlag()); assertTrue(statusCommand.isSuccess()); assertEquals(BinaryCommand.STATUS, statusCommand.getType()); } @Test public void testBinaryStatusCommandFailure() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test failure status BinaryStatusCommand original = new BinaryStatusCommand(2, false); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryStatusCommand); BinaryStatusCommand statusCommand = (BinaryStatusCommand) readCommand; assertEquals(2, statusCommand.getFlag()); assertTrue(!statusCommand.isSuccess()); } @Test public void testBinaryNumberMapDataCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create test data with various number types Map data = new HashMap<>(); data.put("intValue", 42); data.put("longValue", 9876543210L); data.put("floatValue", 3.14f); data.put("doubleValue", 2.71828); data.put("bigIntValue", new BigInteger("123456789012345678901234567890")); data.put("bigDecValue", new BigDecimal("12345.67890123456789")); // Create and write command BinaryNumberMapDataCommand original = new BinaryNumberMapDataCommand("TestNumberMap", data); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryNumberMapDataCommand); BinaryNumberMapDataCommand mapCommand = (BinaryNumberMapDataCommand) readCommand; assertEquals("TestNumberMap", mapCommand.getName()); assertEquals(BinaryCommand.NUMBER_MAP, mapCommand.getType()); Map readData = mapCommand.getData(); assertEquals(6, readData.size()); assertEquals(42, readData.get("intValue").intValue()); assertEquals(9876543210L, readData.get("longValue").longValue()); assertEquals(3.14f, readData.get("floatValue").floatValue(), 0.001); assertEquals(2.71828, readData.get("doubleValue").doubleValue(), 0.00001); assertEquals( new BigInteger("123456789012345678901234567890"), readData.get("bigIntValue")); assertEquals(new BigDecimal("12345.67890123456789"), readData.get("bigDecValue")); } @Test public void testBinaryNumberMapDataCommandEmpty() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command with empty map Map data = new HashMap<>(); BinaryNumberMapDataCommand original = new BinaryNumberMapDataCommand("EmptyMap", data); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryNumberMapDataCommand); BinaryNumberMapDataCommand mapCommand = (BinaryNumberMapDataCommand) readCommand; assertEquals("EmptyMap", mapCommand.getName()); assertTrue(mapCommand.getData().isEmpty()); } @Test public void testBinaryStringMapDataCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create test data Map data = new HashMap<>(); data.put("key1", "value1"); data.put("key2", "value2"); data.put("key3", "value3"); // Create and write command BinaryStringMapDataCommand original = new BinaryStringMapDataCommand("TestStringMap", data); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryStringMapDataCommand); BinaryStringMapDataCommand mapCommand = (BinaryStringMapDataCommand) readCommand; assertEquals("TestStringMap", mapCommand.getName()); assertEquals(BinaryCommand.STRING_MAP, mapCommand.getType()); Map readData = mapCommand.getData(); assertEquals(3, readData.size()); assertEquals("value1", readData.get("key1")); assertEquals("value2", readData.get("key2")); assertEquals("value3", readData.get("key3")); } @Test public void testBinaryNumberDataCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test with Integer BinaryNumberDataCommand original = new BinaryNumberDataCommand("TestInt", 42); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryNumberDataCommand); BinaryNumberDataCommand numberCommand = (BinaryNumberDataCommand) readCommand; assertEquals("TestInt", numberCommand.getName()); assertEquals(42, numberCommand.getValue().intValue()); assertEquals(BinaryCommand.NUMBER, numberCommand.getType()); } @Test public void testBinaryNumberDataCommandDouble() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test with Double BinaryNumberDataCommand original = new BinaryNumberDataCommand("TestDouble", 3.14159); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryNumberDataCommand); BinaryNumberDataCommand numberCommand = (BinaryNumberDataCommand) readCommand; assertEquals("TestDouble", numberCommand.getName()); assertEquals(3.14159, numberCommand.getValue().doubleValue(), 0.00001); } @Test public void testBinaryNumberDataCommandLong() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test with Long BinaryNumberDataCommand original = new BinaryNumberDataCommand("TestLong", 9876543210L); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryNumberDataCommand); BinaryNumberDataCommand numberCommand = (BinaryNumberDataCommand) readCommand; assertEquals("TestLong", numberCommand.getName()); assertEquals(9876543210L, numberCommand.getValue().longValue()); } @Test public void testBinaryNumberDataCommandBigDecimal() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Test with BigDecimal BinaryNumberDataCommand original = new BinaryNumberDataCommand("TestBigDecimal", new BigDecimal("12345.67890123456789")); BinaryWireIO.write(baos, original); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); assertTrue(readCommand instanceof BinaryNumberDataCommand); BinaryNumberDataCommand numberCommand = (BinaryNumberDataCommand) readCommand; assertEquals("TestBigDecimal", numberCommand.getName()); assertEquals(new BigDecimal("12345.67890123456789"), numberCommand.getValue()); } @Test public void testBinaryRetransformationStartNotification() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryRetransformationStartNotification original = new BinaryRetransformationStartNotification(150); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryRetransformationStartNotification); BinaryRetransformationStartNotification notification = (BinaryRetransformationStartNotification) readCommand; assertEquals(150, notification.getNumClasses()); assertEquals(BinaryCommand.RETRANSFORMATION_START, notification.getType()); } @Test public void testBinaryRetransformClassNotification() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryRetransformClassNotification original = new BinaryRetransformClassNotification( "com.example.MyClass", 10, 150 ); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryRetransformClassNotification); BinaryRetransformClassNotification notification = (BinaryRetransformClassNotification) readCommand; assertEquals("com.example.MyClass", notification.getClassName()); assertEquals(10, notification.getIndex()); assertEquals(150, notification.getTotal()); assertEquals(BinaryCommand.RETRANSFORM_CLASS, notification.getType()); } @Test public void testBinarySetSettingsCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create test settings Map settings = new HashMap<>(); settings.put("debug", true); settings.put("timeout", 5000); settings.put("threshold", 1.5); settings.put("name", "TestProbe"); // Create and write command BinarySetSettingsCommand original = new BinarySetSettingsCommand(settings); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinarySetSettingsCommand); BinarySetSettingsCommand settingsCommand = (BinarySetSettingsCommand) readCommand; assertEquals(BinaryCommand.SET_PARAMS, settingsCommand.getType()); Map readSettings = settingsCommand.getParams(); assertEquals(4, readSettings.size()); assertEquals(true, readSettings.get("debug")); assertEquals(5000, readSettings.get("timeout")); assertEquals(1.5, ((Number) readSettings.get("threshold")).doubleValue(), 0.001); assertEquals("TestProbe", readSettings.get("name")); } @Test public void testBinaryListProbesCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create test probe list List probes = new ArrayList<>(); probes.add("probe1"); probes.add("probe2"); probes.add("probe3"); // Create and write command BinaryListProbesCommand original = new BinaryListProbesCommand(); original.setProbes(probes); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryListProbesCommand); BinaryListProbesCommand listCommand = (BinaryListProbesCommand) readCommand; assertEquals(BinaryCommand.LIST_PROBES, listCommand.getType()); List readProbes = listCommand.getProbes(); assertEquals(3, readProbes.size()); assertEquals("probe1", readProbes.get(0)); assertEquals("probe2", readProbes.get(1)); assertEquals("probe3", readProbes.get(2)); } @Test public void testBinaryListProbesCommandEmpty() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command with empty list BinaryListProbesCommand original = new BinaryListProbesCommand(); original.setProbes(new ArrayList<>()); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryListProbesCommand); BinaryListProbesCommand listCommand = (BinaryListProbesCommand) readCommand; assertTrue(listCommand.getProbes().isEmpty()); } @Test public void testBinaryDisconnectCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryDisconnectCommand original = new BinaryDisconnectCommand("probe-12345"); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryDisconnectCommand); BinaryDisconnectCommand disconnectCommand = (BinaryDisconnectCommand) readCommand; assertEquals("probe-12345", disconnectCommand.getProbeId()); assertEquals(BinaryCommand.DISCONNECT, disconnectCommand.getType()); } @Test public void testBinaryReconnectCommand() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create and write command BinaryReconnectCommand original = new BinaryReconnectCommand("probe-67890"); BinaryWireIO.write(baos, original); // Read command ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); BinaryCommand readCommand = BinaryWireIO.read(bais); // Verify command assertTrue(readCommand instanceof BinaryReconnectCommand); BinaryReconnectCommand reconnectCommand = (BinaryReconnectCommand) readCommand; assertEquals("probe-67890", reconnectCommand.getProbeId()); assertEquals(BinaryCommand.RECONNECT, reconnectCommand.getType()); } } ================================================ FILE: btrace-dist/build.gradle ================================================ plugins { id 'java' id 'maven-publish' id 'signing' alias(libs.plugins.shadow) alias(libs.plugins.ospackage) alias(libs.plugins.sdkman.vendors) } import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.internal.os.OperatingSystem repositories { mavenCentral() } def distBase = "${project.buildDir}/resources/main" def distTarget = "${distBase}/v${project.version}" def libsDir = new File("${distTarget}/libs") def includes = [:] java { toolchain { languageVersion.set(JavaLanguageVersion.of(8)) } } includes['agent'] = { if (it.directory) { return true } if (it.path.endsWith('.jar')) { return true } return it.path.startsWith('org/jctools/') || it.path.startsWith('org/openjdk/btrace/agent/') || it.path.startsWith('org/openjdk/btrace/instr/') || it.path.startsWith('org/openjdk/btrace/runtime/') || it.path.startsWith('org/openjdk/btrace/extension/') || it.path.startsWith('org/openjdk/btrace/core/comm/') || it.path.startsWith('org/openjdk/btrace/core/handlers/') || // core/extensions moved to shared section // include the messages resource bundle and class in the agent jar (bootstrap can't load resources) it.path == 'org/openjdk/btrace/core/Messages.class' || it.path == 'org/openjdk/btrace/core/messages.properties' } includes['boot'] = { if (it.directory) { return true } if (it.path.endsWith('.jar')) { return true } if (it.path.startsWith('org/openjdk/btrace/core/')) { if (it.path == 'org/openjdk/btrace/core/Messages.class' || it.path == 'org/openjdk/btrace/core/messages.properties') { // messages resource bundle&class are hoisted to agent and client jars return false } return true } if (it.path.startsWith('org/objectweb/asm/')) { if (it.path.startsWith('org/objectweb/asm/commons/') || it.path.startsWith('org/objectweb/asm/util/') || it.path.startsWith('org/objectweb/asm/xml/')) { return false } return true } if (it.path.startsWith('META-INF/services')) { return !it.path.contains('com.sun.') && !it.path.contains('javax.annotation.') } // btrace-runtime classes should ONLY be in .classdata form (agent section), not as regular .class files return it.path.startsWith("org/slf4j/") || it.path.startsWith('org/openjdk/btrace/extension/') || it.path.startsWith('org/jctools/') // Extensions are loaded from extensions/ directory } includes['client'] = { if (it.directory) { return true } if (it.path.endsWith('.jar')) { return true } if (it.path.startsWith('org/openjdk/btrace')) { return !it.path.startsWith('org/openjdk/btrace/agent') } if (it.path.startsWith('com/googlecode/lanterna/')) { return true } if (it.path.startsWith('META-INF/services')) { return !it.path.contains('com.sun.') && !it.path.contains('javax.annotation.') } return it.path.startsWith("org/slf4j") || it.path.startsWith('org/objectweb/asm') || it.path.startsWith('org/jctools') } configurations { artifact.extendsFrom implementation } dependencies { implementation project(':btrace-agent') implementation project(':btrace-client') implementation project(':btrace-compiler') // Metrics extension excluded unless present; extensions copied dynamically implementation project(':btrace-ext-cli') } jar { onlyIf { false } // Disable default jar task } sourcesJar { onlyIf { false } } sourceSets { main { output.resourcesDir = file("${distTarget}") } } artifacts { archives file("${distTarget}/libs/btrace.jar") } // Intermediate shadow jar with all dependencies and relocations // This is NOT an output artifact, just used for building the masked jar task allClassesShadow(type: ShadowJar) { group 'Build' archiveBaseName.set('btrace-all-temp') archiveVersion.set('') archiveClassifier.set('shadow') destinationDirectory = file("${project.buildDir}/tmp") configurations = [project.configurations.artifact] // Relocate dependencies relocate 'org.jctools', 'org.openjdk.btrace.libs.org.jctools' relocate 'org.objectweb.asm', 'org.openjdk.btrace.libs.org.objectweb.asm' relocate 'org.slf4j', 'org.openjdk.btrace.libs.org.slf4j' // Don't create a manifest for this intermediate jar manifest { attributes([:]) } } task apiJar(type: Jar) { group 'Build' description 'Creates API JAR for compile-time dependencies' archiveBaseName.set('btrace-api') archiveVersion.set('') archiveClassifier.set('') destinationDirectory = libsDir from(project(':btrace-api').sourceSets.main.output) } // uberJar removed - btrace.jar (masked jar) is the single source of truth // ==================== Masked JAR ==================== // Single JAR with .classdata masking for bootstrap isolation // Bootstrap classes are stored as .class, non-bootstrap as .classdata // Uses existing shadow JARs to get properly relocated classes def maskedJarTmpDir = new File(project.buildDir, "masked-jar-tmp") // Task to prepare agent classes with .classdata extension task prepareAgentClassdata(type: Copy) { group 'Build' description 'Prepare agent classes with .classdata extension' dependsOn allClassesShadow, processResources into "${maskedJarTmpDir}/agent" from(provider { zipTree(allClassesShadow.archiveFile) }) { include 'org/openjdk/btrace/libs/org/jctools/**/*.class' include 'org/openjdk/btrace/agent/**/*.class' include 'org/openjdk/btrace/instr/**/*.class' include 'org/openjdk/btrace/instr/jaxb.index' // Required for JAXB probe descriptor loading include 'org/openjdk/btrace/runtime/**/*.class' include 'org/openjdk/btrace/extension/**/*.class' include 'org/openjdk/btrace/core/comm/**/*.class' include 'org/openjdk/btrace/core/handlers/**/*.class' include 'org/openjdk/btrace/core/Messages.class' include 'org/openjdk/btrace/core/messages.properties' // Exclude classes that go in shared section exclude 'org/openjdk/btrace/core/extensions/**' exclude 'org/openjdk/btrace/core/annotations/**' // Exclude bootstrap runtime classes (now in btrace-core) exclude 'org/openjdk/btrace/runtime/BTraceRuntimeAccess.class' exclude 'org/openjdk/btrace/runtime/BTraceRuntimeAccess$*.class' exclude 'org/openjdk/btrace/runtime/LinkingFlag.class' exclude 'META-INF/**' rename { name -> name.endsWith('.class') ? name.replace('.class', '.classdata') : name } } duplicatesStrategy = DuplicatesStrategy.EXCLUDE } // Task to prepare client classes with .classdata extension task prepareClientClassdata(type: Copy) { group 'Build' description 'Prepare client classes with .classdata extension' dependsOn allClassesShadow, processResources into "${maskedJarTmpDir}/client" from(provider { zipTree(allClassesShadow.archiveFile) }) { include 'org/openjdk/btrace/client/**/*.class' include 'org/openjdk/btrace/compiler/**/*.class' include 'org/openjdk/btrace/libs/org/jctools/**/*.class' include 'com/googlecode/lanterna/**/*.class' // Exclude classes that are in bootstrap or shared exclude 'org/openjdk/btrace/core/**' exclude 'org/openjdk/btrace/runtime/**' exclude 'org/openjdk/btrace/extension/**' exclude 'org/openjdk/btrace/libs/org/objectweb/asm/**' exclude 'org/openjdk/btrace/libs/org/slf4j/**' exclude 'META-INF/**' rename { name -> name.endsWith('.class') ? name.replace('.class', '.classdata') : name } } // Messages is excluded from the core/** block above but is required by Main.usage(). // It cannot be in bootstrap (Messages.class.getClassLoader() would return null there, // breaking ResourceBundle.getBundle()), so it must be in the client masked section. from(provider { zipTree(allClassesShadow.archiveFile) }) { include 'org/openjdk/btrace/core/Messages.class' include 'org/openjdk/btrace/core/messages.properties' rename { name -> name.endsWith('.class') ? name.replace('.class', '.classdata') : name } } duplicatesStrategy = DuplicatesStrategy.EXCLUDE } // Task to prepare shared classes with .classdata extension (used by both agent and client) task prepareSharedClassdata(type: Copy) { group 'Build' description 'Prepare shared classes with .classdata extension (for both agent and client)' dependsOn allClassesShadow, processResources into "${maskedJarTmpDir}/shared" from(provider { zipTree(allClassesShadow.archiveFile) }) { // Only include classes used by both client and agent include 'org/openjdk/btrace/core/comm/**/*.class' include 'org/openjdk/btrace/core/extensions/**/*.class' include 'org/openjdk/btrace/core/annotations/**/*.class' include 'org/openjdk/btrace/libs/org/objectweb/asm/**/*.class' // Exclude ASM packages not needed exclude 'org/openjdk/btrace/libs/org/objectweb/asm/commons/**' exclude 'org/openjdk/btrace/libs/org/objectweb/asm/util/**' exclude 'org/openjdk/btrace/libs/org/objectweb/asm/xml/**' exclude 'META-INF/**' rename { name -> name.endsWith('.class') ? name.replace('.class', '.classdata') : name } } duplicatesStrategy = DuplicatesStrategy.EXCLUDE } // Single JAR with .classdata masking for bootstrap isolation task btraceJar(type: Jar) { group 'Build' description 'Creates single JAR with .classdata masking for bootstrap isolation' archiveBaseName.set('btrace') archiveVersion.set('') archiveClassifier.set('') destinationDirectory = libsDir dependsOn prepareAgentClassdata, prepareClientClassdata, prepareSharedClassdata dependsOn project(':btrace-boot').tasks.jar dependsOn project(':btrace-bootstrap').tasks.shadowJar // 1. Include boot loader class (must be in bootstrap as .class) from(project(':btrace-boot').sourceSets.main.output) { include 'org/openjdk/btrace/boot/**/*.class' } // 2. Include bootstrap classes from btrace-bootstrap shadowJar (as .class) from(zipTree(project(':btrace-bootstrap').tasks.shadowJar.archiveFile)) { include '**/*.class' include 'META-INF/services/**' include '**/jaxb.index' // Required for JAXB probe descriptor loading exclude 'org/openjdk/btrace/core/comm/**' // Include annotations in bootstrap (needed by ExtensionIndy at runtime) // Exclude manifest from bootstrap jar exclude 'META-INF/MANIFEST.MF' } // 3. Include masked agent classes (as .classdata) into('META-INF/btrace/agent') { from("${maskedJarTmpDir}/agent") { include '**/*.classdata' include '**/messages.properties' include '**/jaxb.index' // Required for JAXB probe descriptor loading } } // 4. Include masked client classes (as .classdata) into('META-INF/btrace/client') { from("${maskedJarTmpDir}/client") { include '**/*.classdata' include '**/messages.properties' } } // 5. Include shared classes (as .classdata) - used by both agent and client into('META-INF/btrace/shared') { from("${maskedJarTmpDir}/shared") { include '**/*.classdata' } } // 6. Include extensions in root (needed by bootstrap) // Note: Messages class is NOT included in bootstrap - it uses ResourceBundle.getBundle() // with Messages.class.getClassLoader() which returns null for bootstrap classes, // causing NPE. Messages is only in agent/client sections where classloader is non-null. from(project(':btrace-core').sourceSets.main.output) { include 'org/openjdk/btrace/core/extensions/**/*.class' } manifest { attributes( 'Premain-Class': 'org.openjdk.btrace.boot.Loader', 'Agent-Class': 'org.openjdk.btrace.boot.Loader', 'Main-Class': 'org.openjdk.btrace.boot.Loader', 'Can-Redefine-Classes': true, 'Can-Retransform-Classes': true, 'Boot-Class-Path': 'btrace.jar', 'BTrace-Version': version, 'BTrace-Agent-Main': 'org.openjdk.btrace.agent.Main', 'BTrace-Client-Main': 'org.openjdk.btrace.client.Main' ) } duplicatesStrategy = DuplicatesStrategy.EXCLUDE } task fixPermissions(type: Exec) { onlyIf { !OperatingSystem.current().isWindows() } commandLine 'chmod', '500', "${distTarget}/bin/btrace" commandLine 'chmod', '500', "${distTarget}/bin/btracec" commandLine 'chmod', '500', "${distTarget}/bin/btracer" commandLine 'chmod', '500', "${distTarget}/bin/btracex" dependsOn processResources } task copyDtraceLib(type: Copy) { from "${projectDir}/../btrace-dtrace/build/dtrace/libs" into "${distTarget}/libs/" } task copyExtensions(type: Copy) { group 'Build' description 'Copy extension ZIP archives to extensions/ directory' into "${distTarget}/extensions/" } // Copy the btracex CLI shaded jar into libs // Discover all extension subprojects and wire copyExtensions dynamically to avoid hard-coded paths gradle.projectsEvaluated { def extProjects = rootProject.subprojects.findAll { it.plugins.hasPlugin('org.openjdk.btrace.extension') } if (extProjects.isEmpty()) { logger.lifecycle("[btrace-dist] No BTrace extension projects found. Skipping extension packaging.") } else { tasks.named('copyExtensions').configure { t -> extProjects.each { sp -> // Depend on packaging task and copy any *-extension.zip from its distributions folder t.dependsOn("${sp.path}:packageExtension") t.from(sp.layout.buildDirectory.dir('distributions')) { include "*-extension.zip" } } } } } task explodeExtensions { group 'Build' description 'Explode extension ZIP archives into extension subdirectories' dependsOn copyExtensions doLast { def extensionsDir = file("${distTarget}/extensions") if (!extensionsDir.exists()) { return } extensionsDir.listFiles().each { file -> if (file.name.endsWith('-extension.zip')) { // Extract extension name from filename: btrace-metrics-2.3.0-SNAPSHOT-extension.zip -> btrace-metrics def extensionName = file.name.replaceAll(/-\d+.*-extension\.zip$/, '') def targetDir = new File(extensionsDir, extensionName) targetDir.mkdirs() // Explode ZIP into extension directory copy { from zipTree(file) into targetDir } logger.info("Exploded extension ${file.name} into ${extensionName}/") } } } } task buildZip(type: Zip) { dependsOn btraceJar, fixPermissions, explodeExtensions from "${distTarget}" include "**/*" archiveBaseName.set('btrace') archiveVersion.set("v${project.version}") archiveClassifier.set("bin") destinationDirectory = new File(project.buildDir, "distributions") } task buildSdkmanZip(type: Zip) { from "${distBase}" include "**/*" archiveBaseName.set('btrace') archiveVersion.set("v${project.version}") archiveClassifier.set("sdkman-bin") destinationDirectory = new File(project.buildDir, "distributions") } task buildTgz(type: Tar) { into ('/'){ from "${distTarget}" include '**/*' } archiveBaseName.set('btrace') archiveVersion.set("v${project.version}") archiveClassifier.set("bin") destinationDirectory = new File(project.buildDir, "distributions") archiveExtension.set('tar.gz') compression = Compression.GZIP } ospackage { packageName = 'btrace' release = 1 os = LINUX into '/opt/btrace' from("${distTarget}/bin") { into 'bin' fileMode 0550 } from("${distTarget}/libs") { into 'libs' } from("${distTarget}/docs") { into 'docs' } from("${distTarget}/samples") { into 'samples' } link('/usr/local/bin/btrace', '/opt/btrace/bin/btrace') link('/usr/local/bin/btracer', '/opt/btrace/bin/btracer') link('/usr/local/bin/btracec', '/opt/btrace/bin/btracec') link('/usr/local/bin/btracex', '/opt/btrace/bin/btracex') } buildDeb { requires('openjdk-8-jdk') } copyDtraceLib.dependsOn project(':btrace-dtrace').build shadowJar.dependsOn btraceJar, apiJar, copyDtraceLib, copyExtensions buildTgz.dependsOn btraceJar, apiJar, fixPermissions, copyDtraceLib, processResources, explodeExtensions buildZip.dependsOn btraceJar, apiJar, fixPermissions, copyDtraceLib, processResources, explodeExtensions buildSdkmanZip.dependsOn btraceJar, apiJar, fixPermissions, copyDtraceLib, processResources, explodeExtensions buildDeb.dependsOn btraceJar, apiJar, fixPermissions, copyDtraceLib, processResources, explodeExtensions buildRpm.dependsOn btraceJar, apiJar, fixPermissions, copyDtraceLib, processResources, explodeExtensions build.dependsOn buildSdkmanZip, buildZip, buildTgz, buildDeb, buildRpm // Docker build tasks task buildDockerContext(type: Copy, dependsOn: [btraceJar, apiJar, copyDtraceLib, processResources, fixPermissions]) { group 'Docker' description 'Prepare Docker build context with BTrace distribution' from "${distTarget}" into "${buildDir}/docker-context/btrace" fileMode = 0644 doLast { // Copy entrypoint script copy { from "${projectDir}/../docker/docker-entrypoint.sh" into "${buildDir}/docker-context" } // Copy .dockerignore to build context so Docker can use it copy { from "${projectDir}/../docker/.dockerignore" into "${buildDir}/docker-context" } } } task buildDockerImage(type: Exec, dependsOn: buildDockerContext) { group 'Docker' description 'Build main Debian-based Docker image' onlyIf { project.hasProperty('dockerEnabled') && (project.hasProperty('dockerBuildx') ? 'docker buildx version'.execute().waitFor() == 0 : 'docker --version'.execute().waitFor() == 0) } workingDir "${projectDir}/../docker" def buildArgs = [ 'docker', 'build', '--build-arg', "BTRACE_VERSION=${project.version}", '-t', "btrace/btrace:${project.version}", '-t', "btrace/btrace:latest", '-f', 'Dockerfile', "${buildDir}/docker-context" ] if (project.hasProperty('dockerBuildx')) { buildArgs.add(1, 'buildx') buildArgs.addAll(['--platform', 'linux/amd64,linux/arm64']) } commandLine buildArgs } task buildDockerImageAlpine(type: Exec, dependsOn: buildDockerContext) { group 'Docker' description 'Build Alpine-based Docker image' onlyIf { project.hasProperty('dockerEnabled') && (project.hasProperty('dockerBuildx') ? 'docker buildx version'.execute().waitFor() == 0 : 'docker --version'.execute().waitFor() == 0) } workingDir "${projectDir}/../docker" def buildArgs = [ 'docker', 'build', '--build-arg', "BTRACE_VERSION=${project.version}", '-t', "btrace/btrace:${project.version}-alpine", '-t', "btrace/btrace:latest-alpine", '-f', 'Dockerfile.alpine', "${buildDir}/docker-context" ] if (project.hasProperty('dockerBuildx')) { buildArgs.add(1, 'buildx') buildArgs.addAll(['--platform', 'linux/amd64,linux/arm64']) } commandLine buildArgs } task buildDockerImageDistroless(type: Exec, dependsOn: buildDockerContext) { group 'Docker' description 'Build distroless Docker image' onlyIf { project.hasProperty('dockerEnabled') && (project.hasProperty('dockerBuildx') ? 'docker buildx version'.execute().waitFor() == 0 : 'docker --version'.execute().waitFor() == 0) } workingDir "${projectDir}/../docker" def buildArgs = [ 'docker', 'build', '--build-arg', "BTRACE_VERSION=${project.version}", '-t', "btrace/btrace:${project.version}-distroless", '-t', "btrace/btrace:latest-distroless", '-f', 'Dockerfile.distroless', file("${buildDir}/docker-context").absolutePath ] if (project.hasProperty('dockerBuildx')) { buildArgs.add(1, 'buildx') buildArgs.addAll(['--platform', 'linux/amd64']) } commandLine buildArgs } task buildDockerImages { group 'Docker' description 'Build all Docker image variants' dependsOn buildDockerImage, buildDockerImageAlpine, buildDockerImageDistroless } test { doLast { project(':btrace-instr').tasks.test.execute() } } ['agent', 'boot', 'client'].each { name -> // Define a temporary directory for the sources def tempDir = new File(buildDir, "tmp/${name}Sources") // first, we will copy the classes and make sure there are no empty directories tasks.create(name: "${name}CopySources", type: Copy) { doFirst { tempDir.deleteDir() // Ensure the directory is clean } into tempDir duplicatesStrategy = DuplicatesStrategy.EXCLUDE gradle.projectsEvaluated { rootProject.childProjects.each { projectName, siblingProject -> // Skip the current project to avoid duplication if (siblingProject != project) { from(siblingProject.sourceSets.main.allSource) { include includes["${name}"] exclude 'META-INF/**' } } } } doLast { // Recursively delete empty directories def deleteEmptyDirs deleteEmptyDirs = { dir -> dir.eachDir { subDir -> deleteEmptyDirs(subDir) if (!subDir.listFiles().any()) { subDir.deleteDir() } } } deleteEmptyDirs(tempDir) } } // we will use the output of the previous task to crate the JAR tasks.create(name: "${name}SourcesJar", type: Jar, dependsOn: "${name}CopySources") { group 'Documentation' description "Build the btrace-${name} sources jar." archiveAppendix = "${name}" archiveClassifier = "sources" duplicatesStrategy = DuplicatesStrategy.EXCLUDE from tempDir } tasks.create(name: "${name}Javadoc", type: Javadoc) { group 'Documentation' description "Generates Javadoc API documentation for the btrace-${name}." title = "btrace-${name}" destinationDir = file("${project.docsDir}/${name}/javadoc") classpath = files(compileJava.destinationDirectory) + configurations.artifact.asFileTree options.addStringOption('Xdoclint:all,-missing', '-quiet') failOnError false project.parent.subprojects.each { subproject -> // Skip the current project to avoid duplication if (subproject != project) { // Add source from sibling projects based on filter source subproject.sourceSets.main.java.matching { include includes["${name}"] } } } // Include classpath of all sibling projects project.parent.subprojects.each { subproject -> // Skip the current project to avoid duplication if (subproject != project) { classpath += subproject.sourceSets.main.compileClasspath } } } tasks.create(name: "${name}JavadocJar", type: Jar) { group 'Documentation' description "Build the btrace-${name} javadoc jar." archiveAppendix = "${name}" archiveClassifier = "javadoc" from tasks["${name}Javadoc"].getOutputs() } } sdkman { consumerKey = project.hasProperty("sdkman.key") ? project.property("sdkman.key") : System.getenv('SDKMAN_KEY') consumerToken = project.hasProperty("sdkman.token") ? project.property("sdkman.token") : System.getenv('SDKMAN_TOKEN') candidate = "btrace" version = "${project.version}" url = "https://github.com/btraceio/btrace/releases/download/v${project.version}/btrace-v${project.version}-sdkman-bin.zip" hashtag = "btrace" } ["sdkReleaseVersion", "sdkAnnounceVersion"].forEach { tasks[it].onlyIf { !project.version.toString().endsWith("-SNAPSHOT") } } publishing { repositories { maven { // Central Portal URLs (OSSRH was sunset on June 30, 2025) def releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" def snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { // Use Central Portal user token credentials username = project.hasProperty("sonatype.user") ? project.property("sonatype.user") : System.getenv('SONATYPE_USER') password = project.hasProperty("sonatype.password") ? project.property("sonatype.password") : System.getenv('SONATYPE_PASSWORD') } } } publications { btrace(MavenPublication) { artifactId 'btrace' groupId 'io.btrace' artifact btraceJar pom.withXml { addPomDetails(asNode(), 'btrace') } } } } def addPomDetails(node, name) { node.appendNode('name', name) node.appendNode('url', 'https://github.com/btraceio/btrace') node.appendNode('description', 'BTrace: A safe, dynamic tracing tool for the Java platform') def scmNode = node.appendNode('scm') scmNode.appendNode('url', 'https://github.com/btraceio/btrace') scmNode.appendNode('connection', 'scm:git:https://github.com/btraceio/btrace.git') scmNode.appendNode('developerConnection', 'scm:git:https://github.com/btraceio/btrace.git') def licenseNode = node.appendNode('licenses').appendNode('license') licenseNode.appendNode('name', 'GNU General Public License, version 2, with the Classpath Exception') licenseNode.appendNode('url', 'http://openjdk.java.net/legal/gplv2+ce.html') def developerNode = node.appendNode('developers').appendNode('developer') developerNode.appendNode('id', 'jbachorik') developerNode.appendNode('name', 'Jaroslav Bachorik') developerNode.appendNode('email', 'j.bachorik@btrace.io') } signing { def signingKey = project.hasProperty('gpg.signing.key') ? project.property('gpg.signing.key') : System.getenv("GPG_SIGNING_KEY") def signingPwd = project.hasProperty('gpg.signing.pwd') ? project.property('gpg.signing.pwd') : System.getenv("GPG_SIGNING_PWD") if (signingKey != null && signingPwd != null) { useInMemoryPgpKeys(signingKey, signingPwd) } sign publishing.publications.btrace } ================================================ FILE: btrace-dist/src/main/resources/COPYRIGHT ================================================ Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, U.S.A. All rights reserved. Sun Microsystems, Inc. has intellectual property rights relating to technology embodied in the product that is described in this document. In particular, and without limitation, these intellectual property rights may include one or more of the U.S. patents listed at http://www.sun.com/patents and one or more additional patents or pending patent applications in the U.S. and in other countries. U.S. Government Rights - Commercial software. Government users are subject to the Sun Microsystems, Inc. standard license agreement and applicable provisions of the FAR and its supplements. Use is subject to license terms. This distribution may include materials developed by third parties. Sun, Sun Microsystems, the Sun logo, Java, Jini, Solaris and BTrace are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. This product is covered and controlled by U.S. Export Control laws and may be subject to the export or import laws in other countries. Nuclear, missile, chemical biological weapons or nuclear maritime end uses or end users, whether direct or indirect, are strictly prohibited. Export or reexport to countries subject to U.S. embargo or to entities identified on U.S. export exclusion lists, including, but not limited to, the denied persons and specially designated nationals lists is strictly prohibited. Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, Etats-Unis. Tous droits rservs. Sun Microsystems, Inc. dtient les droits de proprit intellectuels relatifs la technologie incorpore dans le produit qui est dcrit dans ce document. En particulier, et ce sans limitation, ces droits de proprit intellectuelle peuvent inclure un ou plus des brevets amricains lists l'adresse http://www.sun.com/patents et un ou les brevets supplmentaires ou les applications de brevet en attente aux Etats - Unis et dans les autres pays. L'utilisation est soumise aux termes de la Licence. Cette distribution peut comprendre des composants dvelopps par des tierces parties. Sun, Sun Microsystems, le logo Sun, Java, Jini, Solaris et BTrace sont des marques de fabrique ou des marques dposes de Sun Microsystems, Inc. aux Etats-Unis et dans d'autres pays. Ce produit est soumis la lgislation amricaine en matire de contrle des exportations et peut tre soumis la rglementation en vigueur dans d'autres pays dans le domaine des exportations et importations. Les utilisations, ou utilisateurs finaux, pour des armes nuclaires,des missiles, des armes biologiques et chimiques ou du nuclaire maritime, directement ou indirectement, sont strictement interdites. Les exportations ou rexportations vers les pays sous embargo amricain, ou vers des entits figurant sur les listes d'exclusion d'exportation amricaines, y compris, mais de manire non exhaustive, la liste de personnes qui font objet d'un ordre de ne pas participer, d'une faon directe ou indirecte, aux exportations des produits ou des services qui sont rgis par la lgislation amricaine en matire de contrle des exportations et la liste de ressortissants spcifiquement dsigns, sont rigoureusement interdites. ================================================ FILE: btrace-dist/src/main/resources/LICENSE ================================================ The GNU General Public License (GPL) Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. One line to give the program's name and a brief idea of what it does. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free software, and you are welcome to redistribute it under certain conditions; type 'show c' for details. The hypothetical commands 'show w' and 'show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than 'show w' and 'show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program 'Gnomovision' (which makes passes at compilers) written by James Hacker. signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. "CLASSPATH" EXCEPTION TO THE GPL Certain source files distributed by Oracle America and/or its affiliates are subject to the following clarification and special exception to the GPL, but only where Oracle has expressly included in the particular source file's header the words "Oracle designates this particular file as subject to the "Classpath" exception as provided by Oracle in the LICENSE file that accompanied this code." Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. [All 3rd party libraries licenses are listed in LICENSE-3RD-PARTY.txt file] ================================================ FILE: btrace-dist/src/main/resources/LICENSE-3RD-PARTY.txt ================================================ [lib/btrace-asm-5.0.4.jar](http://asm.ow2.org/) Copyright (c) 2000-2011 INRIA, France Telecom All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [lib/btrace-jctools-1.2.jar](https://github.com/JCTools/JCTools) [lib/btrace-hppcrt-0.7.4.jar](https://github.com/vsonnier/hppcrt) Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: btrace-dist/src/main/resources/THIRDPARTYLICENSEREADME.txt ================================================ DO NOT TRANSLATE OR LOCALIZE. %% The following software is used by BTrace: ASM 5.0; Use of any of this software is governed by the terms of the license below: /*** * ASM: a very small and fast Java bytecode manipulation framework * Copyright (c) 2000-2011 INRIA, France Telecom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ ================================================ FILE: btrace-dist/src/main/resources/bin/btrace ================================================ #! /bin/sh if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then # resolve links - $0 could be a link to btrace's home PRG="$0" progname=$(basename "$0") PRG=$(readlink "$PRG") if [ -z "$PRG" ]; then PRG=$0 fi BTRACE_HOME=$(dirname "$PRG")/.. BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) fi JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" if [ -d "${JAVA_HOME}/jmods" ]; then JAVA_ARGS="$JAVA_ARGS -XX:+AllowRedefinitionToAddDeleteMethods" JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" fi CLIENT_JAR="${BTRACE_HOME}/libs/btrace.jar" if [ -f "${CLIENT_JAR}" ]; then if [ -d "${JAVA_HOME}" ]; then TOOLS_JAR="$JAVA_HOME/lib/tools.jar" if [ ! -f "$TOOLS_JAR" ]; then # probably running on JRE - try to localize JDK TOOLS_JAR="$JAVA_HOME/../lib/tools.jar" fi if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then # old Java versions on MacOS don't have tools.jar at all case "$(uname)" in Darwin*) # In older JDK versions for Mac OS X, tools.jar is classes.jar # and is kept in a different location. Check if we can locate # classes.jar based on ${JAVA_VERSION} TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" # if we can't find, try relative path from ${JAVA_HOME}. Usually, # /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home # is JAVA_HOME. (or whatever version beyond 1.6.0!) if [ ! -f "${TOOLS_JAR}" ]; then TOOLS_JAR="${JAVA_HOME}/../Classes/classes.jar" fi # If we still can't find, tell the user to set JAVA_VERSION. # This way, we can avoid zip file errors from the agent side # and "connection refused" message from client. if [ ! -f "${TOOLS_JAR}" ]; then echo "Please set JAVA_VERSION to the target java version" exit 1 fi ;; esac fi if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then # in non-jigsaw world TOOLS_JAR must point to a valid file echo "Unable to locate tools.jar. Please, make sure JAVA_HOME points to a valid JDK installation" exit 1 fi ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${CLIENT_JAR}:${TOOLS_JAR}:/usr/share/lib/java/dtrace.jar org.openjdk.btrace.boot.Loader $* else echo "Please set a valid JAVA_HOME before running this script" exit 1 fi else echo "Please set BTRACE_HOME before running this script" exit 1 fi ================================================ FILE: btrace-dist/src/main/resources/bin/btrace.bat ================================================ @echo off rem %~dp0 is expanded pathname of the current script under NT set DEFAULT_BTRACE_HOME=%~dp0.. if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% set DEFAULT_BTRACE_HOME= set CLIENT_JAR=%BTRACE_HOME%\libs\btrace.jar if not exist "%CLIENT_JAR%" goto noBTraceHome if "%JAVA_HOME%" == "" goto noJavaHome set JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" if exist "%JAVA_HOME%/jmods/" ( set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" ) "%JAVA_HOME%/bin/java" "%JAVA_ARGS%" -cp "%CLIENT_JAR%;%JAVA_HOME%/lib/tools.jar" org.openjdk.btrace.boot.Loader %* goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :noBTraceHome echo Please set BTRACE_HOME before running this script :end ================================================ FILE: btrace-dist/src/main/resources/bin/btracec ================================================ #! /bin/sh if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then # resolve links - $0 could be a link to btrace's home PRG="$0" progname=$(basename "$0") PRG=$(readlink "$PRG") if [ -z "$PRG" ]; then PRG=$0 fi BTRACE_HOME=$(dirname "$PRG")/.. BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) fi JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" if [ -d "${JAVA_HOME}/jmods" ]; then JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" fi CLIENT_JAR="${BTRACE_HOME}/libs/btrace.jar" if [ -f "${CLIENT_JAR}" ]; then if [ -d "${JAVA_HOME}" ]; then if [ "$1" = "--version" ]; then ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${CLIENT_JAR} org.openjdk.btrace.boot.Loader --version exit 0 fi TOOLS_JAR="$JAVA_HOME/lib/tools.jar" if [ ! -f "$TOOLS_JAR" ]; then # probably running on JRE - try to localize JDK TOOLS_JAR="$JAVA_HOME/../lib/tools.jar" fi if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then # old Java versions on MacOS don't have tools.jar at all case "$(uname)" in Darwin*) if [ -f /System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar ]; then TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" else TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" fi ;; *) TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" ;; esac fi if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then # in non-jigsaw world TOOLS_JAR must point to a valid file echo "Unable to locate tools.jar. Please, make sure JAVA_HOME points to a valid JDK installation" exit 1 fi ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${CLIENT_JAR}:${TOOLS_JAR} org.openjdk.btrace.compiler.Compiler $* else echo "Please set a valid JAVA_HOME before running this script" exit 1 fi else echo "Please set BTRACE_HOME before running this script" exit 1 fi ================================================ FILE: btrace-dist/src/main/resources/bin/btracec.bat ================================================ @echo off rem %~dp0 is expanded pathname of the current script under NT set DEFAULT_BTRACE_HOME=%~dp0.. if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% set DEFAULT_BTRACE_HOME= set CLIENT_JAR=%BTRACE_HOME%\libs\btrace.jar if not exist "%CLIENT_JAR%" goto noBTraceHome if "%JAVA_HOME%" == "" goto noJavaHome if exist "%JAVA_HOME%/jmods/" ( set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" ) if "%1" == "--version" ( %JAVA_HOME%\bin\java "%JAVA_ARGS%" -cp %CLIENT_JAR% org.openjdk.btrace.boot.Loader --version goto end ) "%JAVA_HOME%/bin/java" "%JAVA_ARGS%" -cp "%CLIENT_JAR%;%JAVA_HOME%/lib/tools.jar" org.openjdk.btrace.boot.Loader %* goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :noBTraceHome echo Please set BTRACE_HOME before running this script :end ================================================ FILE: btrace-dist/src/main/resources/bin/btracep ================================================ #! /bin/sh if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then # resolve links - $0 could be a link to btrace's home PRG="$0" progname=$(basename "$0") PRG=$(readlink "$PRG") if [ -z "$PRG" ]; then PRG=$0 fi BTRACE_HOME=$(dirname "$PRG")/.. BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) fi JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" if [ -d "${JAVA_HOME}/jmods" ]; then JAVA_ARGS="$JAVA_ARGS -XX:+AllowRedefinitionToAddDeleteMethods" JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" fi CLIENT_JAR="${BTRACE_HOME}/libs/btrace.jar" if [ -f "${CLIENT_JAR}" ]; then if [ -d "${JAVA_HOME}" ]; then if [ "$1" = "--version" ]; then ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${CLIENT_JAR} org.openjdk.btrace.boot.Loader --version exit 0 fi TOOLS_JAR="$JAVA_HOME/lib/tools.jar" if [ ! -f "$TOOLS_JAR" ]; then # probably running on JRE - try to localize JDK TOOLS_JAR="$JAVA_HOME/../lib/tools.jar" fi if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then # old Java versions on MacOS don't have tools.jar at all case "$(uname)" in Darwin*) if [ -f /System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar ]; then TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" else TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" fi ;; *) TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" ;; esac fi if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then # in non-jigsaw world TOOLS_JAR must point to a valid file echo "Unable to locate tools.jar. Please, make sure JAVA_HOME points to a valid JDK installation" exit 1 fi ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${CLIENT_JAR}:${TOOLS_JAR} org.openjdk.btrace.client.ProbePrinter $* else echo "Please set a valid JAVA_HOME before running this script" exit 1 fi else echo "Please set BTRACE_HOME before running this script" exit 1 fi ================================================ FILE: btrace-dist/src/main/resources/bin/btracep.bat ================================================ @echo off rem %~dp0 is expanded pathname of the current script under NT set DEFAULT_BTRACE_HOME=%~dp0.. if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% set DEFAULT_BTRACE_HOME= set CLIENT_JAR=%BTRACE_HOME%\libs\btrace.jar if not exist "%CLIENT_JAR%" goto noBTraceHome if "%JAVA_HOME%" == "" goto noJavaHome if exist "%JAVA_HOME%/jmods/" ( set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" ) if "%1" == "--version" ( %JAVA_HOME%\bin\java "%JAVA_ARGS%" -cp %CLIENT_JAR% org.openjdk.btrace.boot.Loader --version goto end ) "%JAVA_HOME%/bin/java" "%JAVA_ARGS%" -cp "%CLIENT_JAR%;%JAVA_HOME%/lib/tools.jar" org.openjdk.btrace.client.ProbePrinter $* goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :noBTraceHome echo Please set BTRACE_HOME before running this script :end ================================================ FILE: btrace-dist/src/main/resources/bin/btracer ================================================ #! /bin/sh if [ -z "$BTRACE_HOME" ] || [ ! -d "$BTRACE_HOME" ]; then # resolve links - $0 could be a link to btrace's home PRG="$0" progname=$(basename "$0") PRG=$(readlink "$PRG") if [ -z "$PRG" ]; then PRG=$0 fi BTRACE_HOME=$(dirname "$PRG")/.. BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) fi usage() { echo "btracer " echo "Options:" echo " --version\t\t\tShow BTrace version" echo " -v\t\t\t\tRun in verbose mode" echo " -u\t\t\t\tRun in unsafe mode" echo " -p \t\t\tBTrace agent server port" echo " -statsd [:]\tUse this StatsD server" echo " -o \t\t\tThe path to a file the btrace agent will store its output" echo " -d \t\t\tDump modified classes to the provided location" echo " -pd \t\t\tSearch for the probe XML descriptors here" echo " --noserver\t\t\tDon't start the socket server" echo " --stdout\t\t\tRedirect the btrace output to stdout instead of writing it to an arbitrary file" echo " -bcp \t\t\tAppend to bootstrap class path" echo " -scp \t\t\tAppend to system class path" echo " -h\t\t\t\tThis message" exit 0 } if [ $# -eq 0 ]; then usage fi JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" if [ -d "${JAVA_HOME}/jmods" ]; then JAVA_ARGS="$JAVA_ARGS -XX:+AllowRedefinitionToAddDeleteMethods" JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" fi CLIENT_JAR="${BTRACE_HOME}/libs/btrace.jar" OPTIONS= if [ -d "${JAVA_HOME}" ]; then while [ true ]; do case $1 in -v) OPTIONS="debug=true,$OPTIONS" ;; -u) OPTIONS="unsafe=true,$OPTIONS" ;; -p) OPTIONS="port=$2,$OPTIONS" shift ;; -d) OPTIONS="dumpClasses=true,dumpDir=$2,$OPTIONS" shift ;; -o) OPTIONS="scriptOutputFile=$2,$OPTIONS" shift ;; -pd) OPTIONS="probeDescPath=$2,$OPTIONS" shift ;; -bcp) OPTIONS="bootClassPath=$2,$OPTIONS" shift ;; -scp) OPTIONS="systemClassPath=$2,$OPTIONS" shift ;; -statsd) OPTIONS="statsd=$2,$OPTIONS" ;; --noserver) OPTIONS="noServer=true,$OPTIONS" ;; --stdout) OPTIONS="stdout=true,$OPTIONS" ;; --version) if [ -z "${CLIENT_JAR}" ]; then echo "Please set BTRACE_HOME before running this script" exit 1 fi $JAVA_HOME/bin/java ${JAVA_ARGS} -cp ${CLIENT_JAR} org.openjdk.btrace.boot.Loader --version exit 0 ;; -h) usage ;; *) break ;; esac shift done if [ -f "${BTRACE_HOME}/libs/btrace-agent.jar" ]; then ${JAVA_HOME}/bin/java -Xshare:off ${JAVA_ARGS} -javaagent:${BTRACE_HOME}/libs/btrace-agent.jar=$OPTIONS,script="$@" else echo "Please set BTRACE_HOME before running this script" fi else echo "Please set a valid JAVA_HOME before running this script" exit 1 fi ================================================ FILE: btrace-dist/src/main/resources/bin/btracer.bat ================================================ @echo off rem %~dp0 is expanded pathname of the current script under NT set DEFAULT_BTRACE_HOME=%~dp0.. if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% set DEFAULT_BTRACE_HOME= if not exist "%BTRACE_HOME%\libs \btrace-agent.jar" goto noBTraceHome set OPTIONS= if "%1"=="" ( call:usage goto end ) if "%JAVA_HOME%" == "" goto noJavaHome if exist "%JAVA_HOME%/jmods/" ( set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" ) if "%1" == "--version" ( set CLIENT_JAR=%BTRACE_HOME%\libs\btrace.jar if not exist "%CLIENT_JAR%" goto noBTraceHome %JAVA_HOME%\bin\java "%JAVA_ARGS%" -cp %CLIENT_JAR% org.openjdk.btrace.boot.Loader --version goto end ) set inloop=1 :loop IF "%1"=="-v" ( set OPTIONS="debug=true,%OPTIONS" goto next ) IF "%1"=="-u" ( set OPTIONS="unsafe=true,%OPTIONS" goto next ) if "%1"=="-p" ( set OPTIONS="port=%2,%OPTIONS" shift goto next ) if "%1"=="-d" ( set OPTIONS="dumpClasses=true,dumpDir=%2,%OPTIONS" shift goto next ) if "%1"=="-o" ( set OPTIONS="scriptOutputFile=%2,%OPTIONS" shift goto next ) if "%1"=="-pd" ( set OPTIONS="probeDescPath=%2,%OPTIONS" shift goto next ) if "%1"=="-bcp" ( OPTIONS="bootClassPath=%2,%OPTIONS" shift goto next ) if "%1"=="-scp" ( set OPTIONS="systemClassPath=%2,%OPTIONS" shift goto next ) if "%1"=="--noserver" ( set OPTIONS="noServer=true,%OPTIONS" goto next ) if "%1"=="--stdout" ( set OPTIONS="stdout=true,%OPTIONS" goto next ) if "%1"=="-statsd" ( set OPTIONS="statsd=%2,%OPTIONS" goto next ) call :usage goto end set inloop=0 :next if %inloop==1 ( shift goto loop ) %JAVA_HOME%\bin\java -Xshare:off "%JAVA_ARGS%" "-javaagent:%BTRACE_HOME%/libs/btrace-agent.jar=%OPTIONS,script=%~1" %2 %3 %4 %5 %6 %7 %8 %9 goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :noBTraceHome echo Please set BTRACE_HOME before running this script goto end :usage echo btracer ^ ^ ^ echo Options: echo -v Run in verbose mode echo -u Run in unsafe mode echo -p ^ BTrace agent server port echo -statsd ^ Use this StatsD server echo -o ^ The path to a file the btrace agent will store its output echo -d ^ Dump modified classes to the provided location echo -pd ^ Search for the probe XML descriptors here echo --noserver Don't start the socket server echo --stdout Redirect the btrace output to stdout instead of writing it to an arbitrary file echo -bcp ^ Append to bootstrap class path echo -scp ^ Append to system class path echo -h This message :end ================================================ FILE: btrace-dist/src/main/resources/bin/btracex ================================================ #! /bin/sh if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then PRG="$0" progname=$(basename "$0") PRG=$(readlink "$PRG") if [ -z "$PRG" ]; then PRG=$0; fi BTRACE_HOME=$(dirname "$PRG")/.. BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) fi JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" CLIENT_JAR="${BTRACE_HOME}/libs/btrace.jar" if [ -f "${CLIENT_JAR}" ]; then if [ -d "${JAVA_HOME}" ]; then CP="${CLIENT_JAR}" ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${CP} org.openjdk.btrace.extcli.Main "$@" else echo "Please set a valid JAVA_HOME before running this script" exit 1 fi else echo "Please set BTRACE_HOME before running this script" exit 1 fi ================================================ FILE: btrace-dist/src/main/resources/bin/btracex.bat ================================================ @echo off setlocal enableextensions if not defined BTRACE_HOME ( set SCRIPT=%~f0 for %%i in ("%SCRIPT%") do set BTRACE_HOME=%%~dpi..\ ) if not defined JAVA_HOME ( echo Please set JAVA_HOME before running this script exit /b 1 ) set JAR=%BTRACE_HOME%\libs\btrace.jar if not exist "%JAR%" ( echo Could not find %JAR% exit /b 1 ) set CP="%JAR%" "%JAVA_HOME%\bin\java" -cp %CP% org.openjdk.btrace.extcli.Main %* endlocal ================================================ FILE: btrace-dist/src/main/resources/conf/extensions.conf ================================================ # BTrace Extension Configuration # # This file controls which extensions are loaded and how they behave. # Configuration is read from (in priority order): # 1. Command-line: -Dbtrace.extensions.config=/path/to/config # 2. User config: ~/.btrace/extensions.conf # 3. System config: $BTRACE_HOME/conf/extensions.conf # # Only the first file found is used (no merging). # ============================================================================ # Extension Control # ============================================================================ # extensions.enabled # Whitelist of extension IDs to load (comma-separated). # If empty or not set, all discovered extensions are enabled. # If set, ONLY listed extensions will be loaded. # # Examples: # extensions.enabled=btrace-metrics # extensions.enabled=btrace-metrics,my-custom-extension # extensions.enabled= # extensions.disabled # Blacklist of extension IDs to prevent from loading (comma-separated). # Takes precedence over extensions.enabled. # Use this to selectively disable specific extensions. # # Examples: # extensions.disabled=btrace-old-version # extensions.disabled=extension-a,extension-b # extensions.disabled= # extensions.autoload # Control when extensions are loaded: # true = Lazy loading (extensions loaded on demand when scripts use them) # false = Eager loading (all enabled extensions loaded at agent startup) # # Default: true # Lazy loading reduces startup time and memory usage. # Eager loading ensures all extensions are available immediately. # extensions.autoload=true # ============================================================================ # Extension-Specific Settings # ============================================================================ # Extension-specific properties use the format: # .= # # These settings are passed to the extension when it's loaded. # Check extension documentation for supported settings. # Example: BTrace Metrics Extension Settings # btrace-metrics.histogram.default-precision=3 # btrace-metrics.histogram.max-value=3600000000 # btrace-metrics.stats.window-size=1000 # Example: Custom Extension Settings # my-extension.feature.enabled=true # my-extension.endpoint=http://localhost:8080 # my-extension.timeout=5000 ================================================ FILE: btrace-dist/src/main/resources/samples/AWTEventTracer.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import java.awt.*; import java.awt.event.FocusEvent; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This simple script traces every AWT focus event in * the target process. */ @BTrace public class AWTEventTracer { @OnMethod( clazz = "java.awt.EventQueue", method = "dispatchEvent" ) public static void onevent(@Self EventQueue queue, AWTEvent event) { if (event instanceof FocusEvent) { println(event); println(); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllCalls1.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TargetMethodOrField; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script demonstrates the possibility to intercept * method calls that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#CALL} * location value. */ @BTrace public class AllCalls1 { @OnMethod(clazz = "javax.swing.JTextField", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) public static void m(@Self Object self, @TargetMethodOrField String method, @ProbeMethodName String probeMethod) { // all calls to the methods with signature "()" println(method + " in " + probeMethod); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllCalls1Sampled.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TargetMethodOrField; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script demonstrates the possibility to intercept * method calls that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#CALL} * location value. *

* Not all instances of the method call are intercepted, however. Adaptive sampling * is used to balance the captured data and incurred overhead. */ @BTrace public class AllCalls1Sampled { @OnMethod(clazz = "javax.swing.JTextField", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) @Sampled(kind = Sampled.Sampler.Adaptive) public static void m(@Self Object self, @TargetMethodOrField String method, @ProbeMethodName String probeMethod) { // all calls to the methods with signature "()" println(method + " in " + probeMethod); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllCalls2.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TargetInstance; import org.openjdk.btrace.core.annotations.TargetMethodOrField; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script demonstrates the possibility to intercept * method calls that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#CALL} * location value. */ @BTrace public class AllCalls2 { @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) public static void n(@Self Object self, @ProbeClassName String pcm, @ProbeMethodName String pmn, @TargetInstance Object instance, @TargetMethodOrField String method, String text) { // all calls to the methods with signature "(String)" println("Context: " + pcm + "#" + pmn + method + " " + text); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllCalls2Sampled.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TargetInstance; import org.openjdk.btrace.core.annotations.TargetMethodOrField; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script demonstrates the possibility to intercept * method calls that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#CALL} * location value. *

* Not all instances of the method call are intercepted, however. Sampling * is used to pick only statistically representative ones. */ @BTrace public class AllCalls2Sampled { @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) @Sampled public static void n(@Self Object self, @ProbeClassName String pcm, @ProbeMethodName String pmn, @TargetInstance Object instance, @TargetMethodOrField String method, String text) { // all calls to the methods with signature "(String)" println("Context: " + pcm + "#" + pmn + method + " " + text); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllCalls3.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.types.AnyType; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.printArray; /** * This script demonstrates the possibility to intercept * method calls that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#CALL} * location value. */ @BTrace public class AllCalls3 { @OnMethod(clazz = "javax.swing.JButton", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) public static void o(@Self Object self, @ProbeMethodName String pmn, AnyType[] args) { // all calls to methods // self - this for the method call // pmn - textual representation of the method // contents of args array: // [0]..[n] - original method call arguments printArray(args); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllCalls3Sampled.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.types.AnyType; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.printArray; /** * This script demonstrates the possibility to intercept * method calls that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#CALL} * location value. *

* Not all instances of the method call are intercepted, however. Adaptive sampling * is used to balance the captured data and incurred overhead. */ @BTrace public class AllCalls3Sampled { @OnMethod(clazz = "javax.swing.JButton", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) @Sampled(kind = Sampled.Sampler.Adaptive) public static void o(@Self Object self, @ProbeMethodName String pmn, AnyType[] args) { // all calls to methods // self - this for the method call // pmn - textual representation of the method // contents of args array: // [0]..[n] - original method call arguments printArray(args); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllLines.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import static org.openjdk.btrace.core.BTraceUtils.print; /* * This sample prints a line every time any line * of code of java.lang.Thread class is reached. * The line param may be set to any particular * value so that the probe fires only when that line * is reached. But, the value -1 means all line numbers. */ @BTrace public class AllLines { @OnMethod( clazz = "java.lang.Thread", location = @Location(value = Kind.LINE, line = -1) ) public static void online(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) { print(pcn + "." + pmn + ":" + line); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllMethods.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Injected; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.services.impl.Printer; /** * This script traces method entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! */ @BTrace public class AllMethods { @Injected private static Printer printer; @OnMethod( clazz = "/javax\\.swing\\..*/", method = "${m}" ) public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { printer.println("this = " + o); printer.print("entered " + probeClass); printer.println("." + probeMethod); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllMethods1.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.*; /** * This script traces method entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! */ @BTrace public class AllMethods1 { @OnMethod( clazz = "/javax\\.swing\\..*/", method = "/.*/" ) public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { println("this = " + o); print("entered " + probeClass); println("." + probeMethod); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllMethodsLevels.java ================================================ /* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.BTraceUtils; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Level; import org.openjdk.btrace.core.annotations.OnEvent; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeMethodName; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script traces method entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! */ @BTrace public class AllMethodsLevels { /** * Capturing only methods invoked from javax.swing.JComponent class. */ @OnMethod( clazz = "javax.swing.JComponent", method = "/.*/", enableAt = @Level("=0") ) public static void l0(@ProbeMethodName(fqn = true) String probeMethod) { println("# " + probeMethod); } /** * This will intercept all the methods from javax.swing.* classes. */ @OnMethod( clazz = "/javax\\.swing\\.*/", method = "/.*/", enableAt = @Level(">=1") ) public static void l1(@ProbeMethodName(fqn = true) String probeMethod) { println("## " + probeMethod); } /** * Switch to level 0. */ @OnEvent("l0") public static void setL0() { BTraceUtils.setInstrumentationLevel(0); } /** * Swtitch to level 1. */ @OnEvent("l1") public static void setL1() { BTraceUtils.setInstrumentationLevel(1); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllMethodsSampled.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.print; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script traces method entry into every method of * every class in javax.swing package. Think before using * this script -- this will slow down your app significantly!! *

* Not all calls are intercepted, however. Sampling * is used to pick only statistically representative ones. */ @BTrace public class AllMethodsSampled { @OnMethod( clazz = "/javax\\.swing\\..*/", method = "/.*/" ) @Sampled public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { println("this = " + o); print("entered " + probeClass); println("." + probeMethod); } } ================================================ FILE: btrace-dist/src/main/resources/samples/AllSync.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Where; import static org.openjdk.btrace.core.BTraceUtils.identityStr; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script traces method/block entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! * Note tha Where.BEFORE is default. For synchronized blocks, BEFORE * means before "monitorenter" bytecode. For synchronized methods, we * can not have probe point Where.BEFORE. Lock is acquired before entering * synchronized method. By making the probe point Where.AFTER for SYNC_ENTER, * we probe after monitorenter bytecode or synchronized method entry. */ @BTrace public class AllSync { @OnMethod( clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(value = Kind.SYNC_ENTRY, where = Where.AFTER) ) public static void onSyncEntry(Object obj) { println("after synchronized entry: " + identityStr(obj)); } @OnMethod( clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(Kind.SYNC_EXIT) ) public static void onSyncExit(Object obj) { println("before synchronized exit: " + identityStr(obj)); } } ================================================ FILE: btrace-dist/src/main/resources/samples/ArgArray.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.types.AnyType; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import static org.openjdk.btrace.core.BTraceUtils.printArray; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This sample demonstrates regular expression * probe matching and getting input arguments * as an array - so that any overload variant * can be traced in "one place". This example * traces any "readXX" method on any class in * java.io package. Probed class, method and arg * array is printed in the action. */ @BTrace public class ArgArray { @OnMethod( clazz = "/java\\.io\\..*/", method = "/read.*/" ) public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) { println(pcn); println(pmn); printArray(args); } } ================================================ FILE: btrace-dist/src/main/resources/samples/Classload.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Return; import static org.openjdk.btrace.core.BTraceUtils.*; /** * A simple BTrace program that prints stack trace * whenever a class is loaded by a user-defined * class loader. We insert a return point probe in * ClassLoader.defineClass method to detect successful * class load. */ @BTrace public class Classload { @OnMethod( clazz = "+java.lang.ClassLoader", method = "defineClass", location = @Location(Kind.RETURN) ) public static void defineclass(@Return Class cl) { println("loaded " + Reflective.name(cl)); Threads.jstack(); println("=========================="); } } ================================================ FILE: btrace-dist/src/main/resources/samples/CommandArg.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import static org.openjdk.btrace.core.BTraceUtils.*; /** * This BTrace program demonstrates command line * arguments. $ method helps in getting command line * arguments. In this example, desired thread name is * passed from the command line (of the BTrace client). */ @BTrace public class CommandArg { @OnMethod( clazz = "java.lang.Thread", method = "run" ) public static void started() { if (Strings.strcmp(Threads.name(Threads.currentThread()), Sys.$(2)) == 0) { println("started " + Sys.$(2)); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/DTraceInline.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.DTrace; import org.openjdk.btrace.core.annotations.OnMethod; import static org.openjdk.btrace.core.BTraceUtils.*; /* * This sample demonstrates DTrace/BTrace integration. * A one-liner D-script is started by BTrace client * because of @DTrace annotation. In this example * on new Java Thread starts, BTrace action raises a * DTrace probe. The D-script prints mixed mode stack * trace on receiving this probe. */ @DTrace("btrace$1:::event / copyinstr(arg0) == \"mstack\" / { jstack(); }") @BTrace public class DTraceInline { @OnMethod( clazz = "java.lang.Thread", method = "start" ) public static void newThread(Thread th) { println(Threads.name(th)); D.probe("mstack", ""); } } ================================================ FILE: btrace-dist/src/main/resources/samples/DTraceRefDemo.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.DTraceRef; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import static org.openjdk.btrace.core.BTraceUtils.*; /* * This sample demonstrates associating a D-script * with a BTrace program using @DTraceRef annotation. * BTrace client looks for absolute or relative path for * the D-script and submits it to kernel *before* submitting * BTrace program to BTrace agent. */ @DTraceRef("classload.d") @BTrace public class DTraceRefDemo { @OnMethod( clazz = "java.lang.ClassLoader", method = "defineClass" ) public static void defineClass() { println("user defined loader load start"); } @OnMethod( clazz = "java.lang.ClassLoader", method = "defineClass", location = @Location(Kind.RETURN) ) public static void defineclass(Class cl) { println("loaded " + Reflective.name(cl)); Threads.jstack(); println("=========================="); } } ================================================ FILE: btrace-dist/src/main/resources/samples/Deadlock.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnTimer; import static org.openjdk.btrace.core.BTraceUtils.Threads.deadlocks; /** * This BTrace program demonstrates deadlocks * built-in function. This example prints * deadlocks (if any) once every 4 seconds. */ @BTrace public class Deadlock { @OnTimer(4000) public static void print() { deadlocks(); } } ================================================ FILE: btrace-dist/src/main/resources/samples/FileTracker.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TLS; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import static org.openjdk.btrace.core.BTraceUtils.*; /** * This sample prints all files opened for read/write * by a Java process. Note that if you pass FileDescriptor * to File{Input/Output}Stream or File{Reader/Writer}, * that is not tracked by this script. */ @BTrace public class FileTracker { @TLS private static String name; @OnMethod( clazz = "java.io.FileInputStream", method = "" ) public static void onNewFileInputStream(@Self FileInputStream self, File f) { name = Strings.str(f); } @OnMethod( clazz = "java.io.FileInputStream", method = "", type = "void (java.io.File)", location = @Location(Kind.RETURN) ) public static void onNewFileInputStreamReturn() { if (name != null) { println("opened for read " + name); name = null; } } @OnMethod( clazz = "java.io.FileOutputStream", method = "" ) public static void onNewFileOutputStream(@Self FileOutputStream self, File f, boolean b) { name = str(f); } @OnMethod( clazz = "java.io.FileOutputStream", method = "", type = "void (java.io.File, boolean)", location = @Location(Kind.RETURN) ) public static void OnNewFileOutputStreamReturn() { if (name != null) { println("opened for write " + name); name = null; } } } ================================================ FILE: btrace-dist/src/main/resources/samples/FileTrackerJfr.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Event; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TLS; import org.openjdk.btrace.core.jfr.JfrEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import static org.openjdk.btrace.core.BTraceUtils.*; import static org.openjdk.btrace.core.BTraceUtils.Jfr.*; /** * This sample prints all files opened for read/write * by a Java process. Note that if you pass FileDescriptor * to File{Input/Output}Stream or File{Reader/Writer}, * that is not tracked by this script. */ @BTrace public class FileTrackerJfr { @Event( name = "fileEvent", label = "BTrace File Event", description = "Sample BTrace file tracker", category = {"btrace", "samples"}, fields = { @Event.Field(type = Event.FieldType.STRING, name = "fileName"), @Event.Field(type = Event.FieldType.STRING, name = "operation") } ) private static JfrEvent.Factory eventFactory; @TLS private static String name; @OnMethod( clazz = "java.io.FileInputStream", method = "" ) public static void onNewFileInputStream(@Self FileInputStream self, File f) { name = Strings.str(f); } @OnMethod( clazz = "java.io.FileInputStream", method = "", type = "void (java.io.File)", location = @Location(Kind.RETURN) ) public static void onNewFileInputStreamReturn() { if (name != null) { JfrEvent event = prepareEvent(eventFactory); setEventField(event, "fileName", name); setEventField(event, "operation", "read"); commit(event); name = null; } } @OnMethod( clazz = "java.io.FileOutputStream", method = "" ) public static void onNewFileOutputStream(@Self FileOutputStream self, File f, boolean b) { name = str(f); } @OnMethod( clazz = "java.io.FileOutputStream", method = "", type = "void (java.io.File, boolean)", location = @Location(Kind.RETURN) ) public static void OnNewFileOutputStreamReturn() { if (name != null) { JfrEvent event = prepareEvent(eventFactory); setEventField(event, "fileName", name); setEventField(event, "operation", "write"); commit(event); name = null; } } } ================================================ FILE: btrace-dist/src/main/resources/samples/FinalizeTracker.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.Self; import java.lang.reflect.Field; import static org.openjdk.btrace.core.BTraceUtils.*; @BTrace public class FinalizeTracker { private static Field fdField = field("java.io.FileInputStream", "fd"); @OnTimer(4000) public static void ontimer() { runFinalization(); } @OnMethod( clazz = "java.io.FileInputStream", method = "finalize" ) public static void onfinalize(@Self Object me) { println(concat("finalizing ", str(me))); printFields(me); printFields(get(fdField, me)); println("=========="); } @OnMethod( clazz = "java.io.FileInputStream", method = "close" ) public static void onclose(@Self Object me) { println(concat("closing ", str(me))); println(concat("thread: ", str(currentThread()))); printFields(me); printFields(get(fdField, me)); jstack(); println("============="); } } ================================================ FILE: btrace-dist/src/main/resources/samples/HistoOnEvent.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnEvent; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import static org.openjdk.btrace.core.BTraceUtils.*; /** * This sample collects histogram of javax.swing.JComponets * created by traced app. But, the histogram is printed only * on event (from client). */ @BTrace public class HistoOnEvent { private static Map histo = newHashMap(); @OnMethod( clazz = "javax.swing.JComponent", method = "" ) public static void onnewObject(@Self Object obj) { String cn = name(classOf(obj)); AtomicInteger ai = get(histo, cn); if (ai == null) { ai = newAtomicInteger(1); put(histo, cn, ai); } else { incrementAndGet(ai); } } @OnEvent public static void print() { if (size(histo) != 0) { printNumberMap("Component Histogram", histo); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/Histogram.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.Self; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import static org.openjdk.btrace.core.BTraceUtils.*; /** * This sample collects histogram of javax.swing.JComponets * created by traced app. The histogram is printed once * every 4 seconds. */ @BTrace public class Histogram { private static Map histo = Collections.newHashMap(); @OnMethod( clazz = "javax.swing.JComponent", method = "" ) public static void onnewObject(@Self Object obj) { String cn = Reflective.name(classOf(obj)); AtomicInteger ai = Collections.get(histo, cn); if (ai == null) { ai = Atomic.newAtomicInteger(1); Collections.put(histo, cn, ai); } else { Atomic.incrementAndGet(ai); } } @OnTimer(4000) public static void print() { if (Collections.size(histo) != 0) { printNumberMap("Component Histogram", histo); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/HistogramBean.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.Property; import org.openjdk.btrace.core.annotations.Self; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import static org.openjdk.btrace.core.BTraceUtils.*; /** * This sample collects histogram of javax.swing.JComponets * created by traced app. The histogram is printed once * every 4 seconds. Also, this example exposes the trace class * as a JMX bean. After connecting BTrace to the target * application, connect VisualVM or jconsole or any other * JMX client to the same application. */ @BTrace public class HistogramBean { // @Property exposes this field as MBean attribute @Property private static Map histo = newHashMap(); @OnMethod( clazz = "javax.swing.JComponent", method = "" ) public static void onnewObject(@Self Object obj) { String cn = name(classOf(obj)); AtomicInteger ai = get(histo, cn); if (ai == null) { ai = newAtomicInteger(1); put(histo, cn, ai); } else { incrementAndGet(ai); } } @OnTimer(4000) public static void print() { if (size(histo) != 0) { printNumberMap("Component Histogram", histo); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/JInfo.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import static org.openjdk.btrace.core.BTraceUtils.*; /* * A simple sample that prints system properties, flags and exits. * This BTrace program mimics the jinfo command line tool in JDK. */ @BTrace public class JInfo { static { println("System Properties:"); printProperties(); println("VM Flags:"); printVmArguments(); println("OS Enviroment:"); printEnv(); exit(0); } } ================================================ FILE: btrace-dist/src/main/resources/samples/JMap.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import static org.openjdk.btrace.core.BTraceUtils.Sys; import static org.openjdk.btrace.core.BTraceUtils.println; /* * A simple sample that dumps heap of the target at start and exits. * This BTrace program mimics the jmap tool (with -dump option). */ @BTrace public class JMap { static { String name; if (Sys.$length() == 3) { name = Sys.$(2); } else { name = "heap.bin"; } Sys.Memory.dumpHeap(name); println("heap dumped!"); Sys.exit(0); } } ================================================ FILE: btrace-dist/src/main/resources/samples/JStack.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import static org.openjdk.btrace.core.BTraceUtils.Sys.exit; import static org.openjdk.btrace.core.BTraceUtils.Threads.deadlocks; import static org.openjdk.btrace.core.BTraceUtils.Threads.jstackAll; /* * A simple sample prints stack traces and exits. This * BTrace program mimics the jstack command line tool in JDK. */ @BTrace public class JStack { static { deadlocks(false); jstackAll(); exit(0); } } ================================================ FILE: btrace-dist/src/main/resources/samples/LogTracer.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import java.lang.reflect.Field; import java.util.logging.LogRecord; import java.util.logging.Logger; import static org.openjdk.btrace.core.BTraceUtils.Reflective; import static org.openjdk.btrace.core.BTraceUtils.println; /** * Simple log message tracer class. This class * prints all log messages regardless of log Level. * Note that we read LogRecord's private "message" * field using "field()" and "objectValue()" built-ins. */ @BTrace public class LogTracer { private static Field msgField = Reflective.field("java.util.logging.LogRecord", "message"); @OnMethod( clazz = "+java.util.logging.Logger", method = "log" ) public static void onLog(@Self Logger self, LogRecord record) { println(Reflective.get(msgField, record)); } } ================================================ FILE: btrace-dist/src/main/resources/samples/MemAlerter.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnLowMemory; import java.lang.management.MemoryUsage; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This sample traces memory threshold exceeds. * You need to specify the memory pool to watch * out and the usage threshold. You can write * script that dumps heap by dumpHeap on crossing * the threshold instead of just printing a message. * Note that the name of the old gen is dependent on * GC algorithm. With ParallelGC, the name is "PS Old Gen". */ @BTrace public class MemAlerter { @OnLowMemory( pool = "Tenured Gen", threshold = 6000000 ) public static void onLowMem(MemoryUsage mu) { println(mu); } } ================================================ FILE: btrace-dist/src/main/resources/samples/Memory.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnTimer; import static org.openjdk.btrace.core.BTraceUtils.Sys; import static org.openjdk.btrace.core.BTraceUtils.println; /** * Simple BTrace program that prints memory * usage once every 4 seconds. It is possible * to modify this to dump heap depending on * used memory crossing a threshold or some other * such condition. [dumpHeap is a built-in function]. */ @BTrace public class Memory { @OnTimer(4000) public static void printMem() { println("Heap:"); println(Sys.Memory.heapUsage()); println("Non-Heap:"); println(Sys.Memory.nonHeapUsage()); } } ================================================ FILE: btrace-dist/src/main/resources/samples/MultiClass.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This BTrace class demonstrates that we can * probe into multiple classes and methods by a * single probe specification using regular * expressions for class and/or method names as * given below. In the example, we put probe into * all readXXX methods of all InputStream classes. */ @BTrace public class MultiClass { @OnMethod( clazz = "/java\\.io\\..*Input.*/", method = "/read.*/" ) public static void onread(@ProbeClassName String pcn) { println("read on " + pcn); } } ================================================ FILE: btrace-dist/src/main/resources/samples/NewArray.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script demonstrates the possibility to intercept * array creations that are about to be executed from the body of * a certain method. This is achieved by using the {@linkplain Kind#NEWARRAY} * location value. */ @BTrace public class NewArray { // component count private static volatile long count; @OnMethod( clazz = "/.*/", // tracking in all classes; can be restricted to specific user classes method = "/.*/", // tracking in all methods; can be restricted to specific user methods location = @Location(value = Kind.NEWARRAY, clazz = "char") ) public static void onnew(@ProbeClassName String pcn, @ProbeMethodName String pmn, String arrType, int dim) { // pcn - allocation place class name // pmn - allocation place method name // **** following two parameters MUST always be in this order // arrType - the actual array type // dim - the array dimension // increment counter on new array count++; } @OnTimer(2000) public static void print() { // print the counter println("char[] count = " + count); } } ================================================ FILE: btrace-dist/src/main/resources/samples/NewComponent.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.Self; import java.awt.*; import static org.openjdk.btrace.core.BTraceUtils.println; /** * A BTrace program that can be run against a GUI * program. This program prints (monotonic) count of * number of java.awt.Components created once every * 2 seconds (2000 milliseconds). */ @BTrace public class NewComponent { // component count private static volatile long count; @OnMethod( clazz = "java.awt.Component", method = "" ) public static void onnew(@Self Component c) { // increment counter on constructor entry count++; } @OnTimer(2000) public static void print() { // print the counter println("component count = " + count); } } ================================================ FILE: btrace-dist/src/main/resources/samples/OnThrow.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TLS; import static org.openjdk.btrace.core.BTraceUtils.Threads; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This example demonstrates printing stack trace * of an exception and thread local variables. This * trace script prints exception stack trace whenever * java.lang.Throwable's constructor returns. This way * you can trace all exceptions that may be caught and * "eaten" silently by the traced program. Note that the * assumption is that the exceptions are thrown soon after * creation [like in "throw new FooException();"] rather * that be stored and thrown later. */ @BTrace public class OnThrow { // store current exception in a thread local // variable (@TLS annotation). Note that we can't // store it in a global variable! @TLS static Throwable currentException; // introduce probe into every constructor of java.lang.Throwable // class and store "this" in the thread local variable. @OnMethod( clazz = "java.lang.Throwable", method = "" ) public static void onthrow(@Self Throwable self) { currentException = self; } @OnMethod( clazz = "java.lang.Throwable", method = "" ) public static void onthrow1(@Self Throwable self, String s) { currentException = self; } @OnMethod( clazz = "java.lang.Throwable", method = "" ) public static void onthrow1(@Self Throwable self, String s, Throwable cause) { currentException = self; } @OnMethod( clazz = "java.lang.Throwable", method = "" ) public static void onthrow2(@Self Throwable self, Throwable cause) { currentException = self; } // when any constructor of java.lang.Throwable returns // print the currentException's stack trace. @OnMethod( clazz = "java.lang.Throwable", method = "", location = @Location(Kind.RETURN) ) public static void onthrowreturn() { if (currentException != null) { Threads.jstack(currentException); println("====================="); currentException = null; } } } ================================================ FILE: btrace-dist/src/main/resources/samples/ProbeArgs.java ================================================ /* * Copyright (c) 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ import org.openjdk.btrace.core.annotations.BTrace; import static org.openjdk.btrace.core.BTraceUtils.Sys; import static org.openjdk.btrace.core.BTraceUtils.println; @BTrace(trusted = true) public class ProbeArgs { static { println("arg#=" + Sys.$length()); for (int i = 0; i < Sys.$length(); i++) { println("#" + i + "=" + Sys.$(i)); } println("switch=" + Sys.$("switch")); } } ================================================ FILE: btrace-dist/src/main/resources/samples/ProbeExit.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnExit; import org.openjdk.btrace.core.annotations.OnTimer; import static org.openjdk.btrace.core.BTraceUtils.Sys; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This program demonstrates OnExit probe. * When some BTrace action method calls "exit(int)" * built-in function, method annotated by @OnExit * (if found) is called. In this method, BTrace script * print summary information of tracing and/or do clean-up. */ @BTrace public class ProbeExit { private static volatile int i; // @OnExit is called when some BTrace method // calls exit(int) method @OnExit public static void onexit(int code) { println("BTrace program exits!"); } // We just put @OnTimer probe and exit BTrace // program when the count reaches 5. @OnTimer(1000) public static void ontime() { println("hello"); i++; if (i == 5) { // note that this exits the BTrace client // and not the traced program (which would // be a destructive action!). Sys.exit(0); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/Profiling.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.Profiler; import org.openjdk.btrace.core.BTraceUtils; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Duration; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Property; /** * This script demonstrates new capabilities built into BTrace 1.2 *

    *
  1. Shortened syntax - when omitting "public" identifier in the class * definition one can safely omit all other modifiers when declaring methods * and variables
  2. *
  3. Extended syntax for @ProbeMethodName annotation - you can use * fqn parameter to request a fully qualified method name instead of * the short one
  4. *
  5. Profiling support - you can use {@linkplain Profiler} instance to gather * performance data with the smallest overhead possible *
* * @since 1.2 */ @BTrace class Profiling { @Property Profiler swingProfiler = BTraceUtils.Profiling.newProfiler(); @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/") void entry(@ProbeMethodName(fqn = true) String probeMethod) { BTraceUtils.Profiling.recordEntry(swingProfiler, probeMethod); } @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(value = Kind.RETURN)) void exit(@ProbeMethodName(fqn = true) String probeMethod, @Duration long duration) { BTraceUtils.Profiling.recordExit(swingProfiler, probeMethod, duration); } @OnTimer(5000) void timer() { BTraceUtils.Profiling.printSnapshot("Swing performance profile", swingProfiler); } } ================================================ FILE: btrace-dist/src/main/resources/samples/Sizeof.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.*; @BTrace public class Sizeof { @OnMethod( clazz = "javax.swing.JComponent", method = "" ) public static void onnew(@Self Object obj) { println(Strings.concat("object of: ", Reflective.name(Reflective.classOf(obj)))); println(Strings.concat("size: ", Strings.str(sizeof(obj)))); } } ================================================ FILE: btrace-dist/src/main/resources/samples/SocketTracker.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Return; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TLS; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.nio.channels.SocketChannel; import static org.openjdk.btrace.core.BTraceUtils.Strings; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This example tracks all server socket creations * and client socket accepts. *
* Also, it shows how to use shared methods. */ @BTrace public class SocketTracker { @TLS private static int port = -1; @TLS private static InetAddress inetAddr; @TLS private static SocketAddress sockAddr; @OnMethod( clazz = "java.net.ServerSocket", method = "" ) public static void onServerSocket(@Self ServerSocket self, int p, int backlog, InetAddress bindAddr) { port = p; inetAddr = bindAddr; } @OnMethod( clazz = "java.net.ServerSocket", method = "", type = "void (int, int, java.net.InetAddress)", location = @Location(Kind.RETURN) ) public static void onSockReturn() { if (port != -1) { println("server socket at " + port); port = -1; } if (inetAddr != null) { println("server socket at " + inetAddr); inetAddr = null; } } @OnMethod( clazz = "java.net.ServerSocket", method = "bind" ) public static void onBind(@Self ServerSocket self, SocketAddress addr, int backlog) { sockAddr = addr; } @OnMethod( clazz = "java.net.ServerSocket", method = "bind", type = "void (java.net.SocketAddress, int)", location = @Location(Kind.RETURN) ) public static void onBindReturn() { socketBound(); } @OnMethod( clazz = "sun.nio.ch.ServerSocketChannelImpl", method = "bind" ) public static void onBind(@Self Object self, SocketAddress addr, int backlog) { sockAddr = addr; } @OnMethod( clazz = "sun.nio.ch.ServerSocketChannelImpl", method = "bind", type = "void (java.net.SocketAddress, int)", location = @Location(Kind.RETURN) ) public static void onBindReturn2() { socketBound(); } @OnMethod( clazz = "java.net.ServerSocket", method = "accept", location = @Location(Kind.RETURN) ) public static void onAcceptReturn(@Return Socket sock) { clientSocketAcc(sock); } @OnMethod( clazz = "sun.nio.ch.ServerSocketChannelImpl", method = "socket", location = @Location(Kind.RETURN) ) public static void onSocket(@Return ServerSocket ssock) { println(Strings.strcat("server socket at ", Strings.str(ssock))); } @OnMethod( clazz = "sun.nio.ch.ServerSocketChannelImpl", method = "accept", location = @Location(Kind.RETURN) ) public static void onAcceptReturn(@Return SocketChannel sockChan) { clientSocketAcc(sockChan); } private static void socketBound() { if (sockAddr != null) { println("server socket bind " + sockAddr); sockAddr = null; } } private static void clientSocketAcc(Object obj) { if (obj != null) { println("client socket accept " + obj); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/SocketTracker1.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.types.AnyType; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnProbe; import org.openjdk.btrace.core.annotations.Return; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.annotations.TLS; import java.net.InetAddress; import java.net.ServerSocket; import java.net.SocketAddress; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This example tracks all server socket creations * and client socket accepts. Unlike SockerTracker.java, * this script uses only public API classes and @OnProbe * probes - which would be mapped to internal implementation * classes by a XML descriptor at BTrace agent. For this * sample, XML probe descriptor is "java.net.socket.xml". */ @BTrace public class SocketTracker1 { @TLS private static int port = -1; @TLS private static InetAddress inetAddr; @TLS private static SocketAddress sockAddr; @OnMethod( clazz = "java.net.ServerSocket", method = "" ) public static void onServerSocket(@Self ServerSocket self, int p, int backlog, InetAddress bindAddr) { port = p; inetAddr = bindAddr; } @OnMethod( clazz = "java.net.ServerSocket", method = "", type = "void (int, int, java.net.InetAddress)", location = @Location(Kind.RETURN) ) public static void onSockReturn() { if (port != -1) { println("server socket at " + port); port = -1; } if (inetAddr != null) { println("server socket at " + inetAddr); inetAddr = null; } } @OnProbe( namespace = "java.net.socket", name = "server-socket-creator" ) public static void onSocket(@Return ServerSocket ssock) { println("server socket at " + ssock); } @OnProbe( namespace = "java.net.socket", name = "bind" ) public static void onBind(@Self Object self, SocketAddress addr, int backlog) { sockAddr = addr; } @OnProbe( namespace = "java.net.socket", name = "bind-return" ) public static void onBindReturn() { if (sockAddr != null) { println("server socket bind " + sockAddr); sockAddr = null; } } @OnProbe( namespace = "java.net.socket", name = "accept-return" ) public static void onAcceptReturn(AnyType sock) { if (sock != null) { println("client socket accept " + sock); } } } ================================================ FILE: btrace-dist/src/main/resources/samples/SubtypeTracer.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import static org.openjdk.btrace.core.BTraceUtils.print; import static org.openjdk.btrace.core.BTraceUtils.println; /** * A simple example that demonstrates subtype matching by +foo pattern * in "clazz" attribute of @OnMethod annotation. */ @BTrace public class SubtypeTracer { @OnMethod( clazz = "+java.lang.Runnable", method = "run" ) public static void onRun(@ProbeClassName String pcn, @ProbeMethodName String pmn) { // on every Runnable.run() method entry print class.method print(pcn); print('.'); println(pmn); } } ================================================ FILE: btrace-dist/src/main/resources/samples/SysProp.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import static org.openjdk.btrace.core.BTraceUtils.Sys; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This BTrace script demonstrates that it is okay * to trace bootstrap classes and call the same * inside the trace actions. In this example, we insert * a probe into System.getProperty() method and call * System.getProperty [through property() built-in function] * without getting into infinite recursion. A thread local * flag is used by BTrace to avoid infinite recursion here. */ @BTrace public class SysProp { @OnMethod( clazz = "java.lang.System", method = "getProperty" ) public static void onGetProperty(String name) { println(name); // call property safely here. println(Sys.Env.property(name)); } } ================================================ FILE: btrace-dist/src/main/resources/samples/Test.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.DTraceRef; @DTraceRef("syscalls.d") @BTrace public class Test { } ================================================ FILE: btrace-dist/src/main/resources/samples/ThreadBean.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This sample demonstrates simple preprocessor in BTrace. * When you run this sample against a Java process, you have * to specify -I . option so that the preprocessor can find * the "btracedefs.h" file: * * btrace -I . ThreadBean.java * * Without -I option in command, BTrace skips preprocessor * invocation. */ #include"btracedefs.h" BTRACE_IMPORT /** * This sample demonstrates that you can expose a BTrace * class as a JMX MBean. After connecting BTrace to the * target application, connect VisualVM or jconsole or * any other JMX client to the same application. */ BTRACE ThreadBean{ // PROPERTY makes the count field to be exposed // as an attribute of this MBean. PROPERTY long count; @OnMethod( clazz = "java.lang.Thread", method = "start" ) ACTION onnewThread(@Self Thread t){ count++; } @OnTimer(2000) ACTION ontimer(){ println(count); } } ================================================ FILE: btrace-dist/src/main/resources/samples/ThreadCounter.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Export; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.Counters; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This sample creates a jvmstat counter and * increments it everytime Thread.start() is * called. This thread count may be accessed * from outside the process. The @Export annotated * fields are mapped to jvmstat counters. The counter * name is "btrace." + + "." + */ @BTrace public class ThreadCounter { // create a jvmstat counter using @Export @Export private static long count; @OnMethod( clazz = "java.lang.Thread", method = "start" ) public static void onnewThread(@Self Thread t) { // updating counter is easy. Just assign to // the static field! count++; } @OnTimer(2000) public static void ontimer() { // we can access counter as "count" as well // as from jvmstat counter directly. println(count); // or equivalently ... println(Counters.perfLong("btrace.org.openjdk.btrace.samples.ThreadCounter.count")); } } ================================================ FILE: btrace-dist/src/main/resources/samples/ThreadCounterBean.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.Property; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.println; /** * This sample demonstrates that you can expose a BTrace * class as a JMX MBean. After connecting BTrace to the * target application, connect VisualVM or jconsole or * any other JMX client to the same application. */ @BTrace public class ThreadCounterBean { // @Property makes the count field to be exposed // as an attribute of this MBean. @Property private static long count; @OnMethod( clazz = "java.lang.Thread", method = "start" ) public static void onnewThread(@Self Thread t) { count++; } @OnTimer(2000) public static void ontimer() { println(count); } } ================================================ FILE: btrace-dist/src/main/resources/samples/ThreadStart.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.Self; import static org.openjdk.btrace.core.BTraceUtils.*; /* * This BTrace script inserts a probe into * method entry of java.lang.Thread.start() method. * At each Thread.start(), it raises a DTrace probe * in addition to printing the name of the thread. * A D-script like jthread.d may be used to get the * associated DTrace probe events. */ @BTrace public class ThreadStart { @OnMethod( clazz = "java.lang.Thread", method = "start" ) public static void onnewThread(@Self Thread t) { D.probe("jthreadstart", Threads.name(t)); println("starting " + Threads.name(t)); } } ================================================ FILE: btrace-dist/src/main/resources/samples/Timers.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.OnTimer; import static org.openjdk.btrace.core.BTraceUtils.*; /** * Demonstrates multiple timer probes with different periods to fire. */ @BTrace public class Timers { // when starting print the target VM version and start time static { println("vm version " + Sys.VM.vmVersion()); println("vm starttime " + Sys.VM.vmStartTime()); } @OnTimer(1000) public static void f() { println("1000 msec: " + Sys.VM.vmUptime()); } @OnTimer(3000) public static void f1() { println("3000 msec: " + Time.millis()); } } ================================================ FILE: btrace-dist/src/main/resources/samples/URLTracker.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.DTraceRef; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.TLS; import java.net.Proxy; import java.net.URL; import static org.openjdk.btrace.core.BTraceUtils.*; /* * This sample prints every Java URL openURL and * openConnection (successful) attempts. In addition, * on platforms where DTrace is available, it runs * the D-script jurls.d -- which collects a histogram * of URL accesses by a btrace:::event probe. From this * BTrace program we raise that DTrace probe (dtraceProbe * call). Note that it is possible to do similar histogram * in BTrace itself (see Histogram.java). But, this sample * shows DTrace/BTrace integration as well. On exit, all * DTrace aggregates are printed by BTrace (i.e., the ones * that are not explicitly printed by DTrace printa call). */ @DTraceRef("jurls.d") @BTrace public class URLTracker { @TLS private static URL url; @OnMethod( clazz = "java.net.URL", method = "openConnection" ) public static void openURL(URL self) { url = self; } @OnMethod( clazz = "java.net.URL", method = "openConnection" ) public static void openURL(URL self, Proxy p) { url = self; } @OnMethod( clazz = "java.net.URL", method = "openConnection", location = @Location(Kind.RETURN) ) public static void openURL() { if (url != null) { println("open " + url); D.probe("java-url-open", Strings.str(url)); url = null; } } } ================================================ FILE: btrace-dist/src/main/resources/samples/WebServiceTracker.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Duration; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import static org.openjdk.btrace.core.BTraceUtils.*; /** * A simple BTrace program that prints a class name * and method name whenever a webservice is called and * also prints time taken by service method. WebService * entry points are annotated javax.jws.WebService and * javax.jws.WebMethod. We insert tracing actions into * every class and method annotated by these annotations. * This way we don't need to know actual webservice * implementor class name. */ @BTrace public class WebServiceTracker { @OnMethod( clazz = "@javax.jws.WebService", method = "@javax.jws.WebMethod" ) public static void onWebserviceEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn) { print("entering webservice "); println(Strings.strcat(Strings.strcat(pcn, "."), pmn)); } @OnMethod( clazz = "@javax.jws.WebService", method = "@javax.jws.WebMethod", location = @Location(Kind.RETURN) ) public static void onWebserviceReturn(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long d) { print("leaving web service "); println(Strings.strcat(Strings.strcat(pcn, "."), pmn)); println(Strings.strcat("Time taken (msec) ", Strings.str(d / 1000))); println("=========================="); } } ================================================ FILE: btrace-dist/src/main/resources/samples/btracedefs.h ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #define BTRACE_UTILS import static org.openjdk.btrace.core.BTraceUtils.*; #define BTRACE_ANNO import org.openjdk.btrace.core.annotations.*; #define BTRACE_IMPORT BTRACE_UTILS \ BTRACE_ANNO #define BTRACE @BTrace public class #define VAR public static #define ACTION public static void #define EXPORT @Export public static #define THREAD @Thread public static #define PROPERTY @Property public static ================================================ FILE: btrace-dist/src/main/resources/samples/classload.d ================================================ /* * This D-script prints one line on each Java class * load and unload. To mark DTrace message begin, this * script prints a message on DTrace session start. */ BEGIN { printf("dtrace start\n"); } hotspot$1:::class-loaded { printf("loaded %s\n", copyinstr(arg0, arg1)); } hotspot$1:::class-unloaded { printf("unloaded %s\n", copyinstr(arg0, arg1)); } ================================================ FILE: btrace-dist/src/main/resources/samples/java.net.socket.xml ================================================ sun.nio.ch.ServerSocketChannelImpl socket RETURN sun.nio.ch.ServerSocketChannelImpl bind void (java.net.SocketAddress, int) java.net.ServerSocket bind void (java.net.SocketAddress, int) sun.nio.ch.ServerSocketChannelImpl bind void (java.net.SocketAddress, int) RETURN java.net.ServerSocket bind void (java.net.SocketAddress, int) RETURN java.net.ServerSocket accept RETURN sun.nio.ch.ServerSocketChannelImpl accept RETURN ================================================ FILE: btrace-dist/src/main/resources/samples/jthread.d ================================================ #!/usr/sbin/dtrace -s btrace$1:::event / copyinstr(arg0) == "jthreadstart" && arg1 != NULL / { printf("From DTrace: Java Thread '%s' started\n", copyinstr(arg1)); } ================================================ FILE: btrace-dist/src/main/resources/samples/jurls.d ================================================ /* * This D-script maintains a aggregation whenever * btrace:::event probe is raised with "java-url-open" * as first argument. */ btrace$target:::event / copyinstr(arg0) == "java-url-open" / { @[copyinstr(arg1)] = count(); } ================================================ FILE: btrace-dist/src/main/resources/samples/syscalls.d ================================================ syscall:::entry / pid == $target / { @[probefunc] = count(); } ================================================ FILE: btrace-dtrace/build.gradle ================================================ import org.gradle.internal.os.OperatingSystem import org.gradle.internal.jvm.Jvm plugins { id 'java' alias(libs.plugins.shadow) } def nativeHeadersDir = "${buildDir}/generated/native/include" def dtraceLibDir = "${buildDir}/dtrace" def javaHome = Jvm.current().javaHome def isSolaris = OperatingSystem.current() == OperatingSystem.SOLARIS def osArch = System.getProperty("os.arch") dependencies { implementation libs.slf4j implementation libs.slf4j.simple implementation project(':btrace-core') } task nativeHeaders(type:Exec) { def classpath = sourceSets.main.runtimeClasspath.asPath commandLine "javah", "-d", nativeHeadersDir, "-classpath", classpath, "org.openjdk.btrace.core.BTraceRuntime" dependsOn classes } if (isSolaris) { task makedirs(type: Exec) { commandLine "mkdir", "-p", "${dtraceLibDir}", "${dtraceLibDir}/libs/${osArch}" } task compileC(type: Exec) { commandLine "gcc", "-c", "-I${javaHome}/include", "-I${javaHome}/include/solaris", "-I${nativeHeadersDir}", "-o${dtraceLibDir}/btrace.o", "${projectDir}/src/main/native/btrace.c" dependsOn nativeHeaders, makedirs } task dtrace(type: Exec) { commandLine "/usr/sbin/dtrace", "-G", "-o", "${dtraceLibDir}/btraced.o", "-s", "${projectDir}/src/main/native/btraced.d", "${dtraceLibDir}/btrace.o" dependsOn compileC, makedirs } task linkDTraceLib(type: Exec) { commandLine "gcc", "-G", "${dtraceLibDir}/btrace.o", "${dtraceLibDir}/btraced.o", "-o", "${dtraceLibDir}/libs/${osArch}/libbtrace.so" dependsOn dtrace, makedirs } } sourceSets { main { java { srcDirs "src/main/java" srcDirs "src/mock/java" } } } shadowJar { include 'org/openjdk/btrace/dtrace/**/*' archiveClassifier = null } sourcesJar { duplicatesStrategy DuplicatesStrategy.EXCLUDE } build.dependsOn shadowJar if (isSolaris) { jar.dependsOn linkDTraceLib } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTrace.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import java.io.File; import java.io.IOException; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.CommandListener; import org.openjdk.btrace.core.comm.ErrorCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.opensolaris.os.dtrace.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple wrapper class around DTrace/Java API. This wrapper accepts a BTrace command listener, * DTrace file or string and DTrace macro arguments. The events from DTrace are wrapped as BTrace * command instances and given to the listener. * * @author A. Sundararajan */ @SuppressWarnings("RedundantThrows") public class DTrace { private static final Logger log = LoggerFactory.getLogger(DTrace.class); /** * Submits a D-script from given file and passes given argument array as DTrace macro arguments. * The events from DTrace are wrapped as BTrace commands and listener given is notified. * * @param file D-script file to submit * @param args DTrace macro arguments * @param listener BTrace command listener that is notified */ public static void submit(File file, String[] args, CommandListener listener) throws DTraceException, IOException { Consumer cons = newConsumer(args, listener); cons.compile(file, args); start(cons, listener); } /** * Submits a D-script string and passes the given argument array as DTrace macro arguments. The * events from DTrace are wrapped as BTrace commands and listener given is notified. * * @param program D-script as a string * @param args DTrace macro arguments * @param listener BTrace command listener that is notified */ public static void submit(String program, String[] args, CommandListener listener) throws DTraceException { Consumer cons = newConsumer(args, listener); cons.compile(program, args); start(cons, listener); } private static void start(Consumer cons, CommandListener listener) throws DTraceException { cons.enable(); cons.go( th -> { try { listener.onCommand(new ErrorCommand(th)); } catch (IOException ioexp) { log.error("Failed to send error command to listener", ioexp); } }); } private static Consumer newConsumer(String[] args, CommandListener listener) throws DTraceException { Consumer cons = new LocalConsumer(); cons.addConsumerListener( new ConsumerAdapter() { private void notify(Command cmd) { try { listener.onCommand(cmd); } catch (IOException ioexp) { log.error("Failed to send command to listener", ioexp); } } @Override public void consumerStarted(ConsumerEvent ce) { notify(new DTraceStartCommand(ce)); } @Override public void consumerStopped(ConsumerEvent ce) { Consumer cons = ce.getSource(); Aggregate ag = null; try { ag = cons.getAggregate(); } catch (DTraceException dexp) { notify(new ErrorCommand(dexp)); } StringBuilder buf = new StringBuilder(); if (ag != null) { for (Aggregation agg : ag.asMap().values()) { String name = agg.getName(); if (name != null && name.length() > 0) { buf.append(name); buf.append('\n'); } for (AggregationRecord rec : agg.asMap().values()) { buf.append('\t'); buf.append(rec.getTuple()); buf.append(" "); buf.append(rec.getValue()); buf.append('\n'); } } } String msg = buf.toString(); if (msg.length() > 0) { notify(new MessageCommand(msg)); } notify(new DTraceStopCommand(ce)); cons.close(); } @Override public void dataReceived(DataEvent de) { notify(new DTraceDataCommand(de)); } @Override public void dataDropped(DropEvent de) { notify(new DTraceDropCommand(de)); } @Override public void errorEncountered(ErrorEvent ee) throws ConsumerException { try { super.errorEncountered(ee); } catch (ConsumerException ce) { notify(new DTraceErrorCommand(ce, ee)); throw ce; } } }); // open DTrace Consumer cons.open(); // unused macro arguments are fine cons.setOption(Option.argref, ""); // if no macro arg passed use "" or NULL cons.setOption(Option.defaultargs, ""); // allow empty D-scripts cons.setOption(Option.empty, ""); // be quiet! equivalent to DTrace's -q cons.setOption(Option.quiet, ""); // undefined user land symbols are fine cons.setOption(Option.unodefs, ""); // allow zero matching of probes (needed for late loading) cons.setOption(Option.zdefs, ""); try { int pid = Integer.parseInt(args[0]); cons.grabProcess(pid); } catch (Exception ignored) { } return cons; } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; /** * A marker interface to tell whether a given BTrace command is a wrapper of a DTrace event. * * @author A. Sundararajan */ public interface DTraceCommand {} ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceConsumerCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.openjdk.btrace.core.comm.EventCommand; import org.opensolaris.os.dtrace.Consumer; import org.opensolaris.os.dtrace.ConsumerEvent; /** * Abstract command to represent consumer event from DTrace. * * @author A. Sundararajan */ public abstract class DTraceConsumerCommand extends EventCommand implements DTraceCommand { private ConsumerEvent ce; public DTraceConsumerCommand(String type, ConsumerEvent ce) { super(type); this.ce = ce; } /** Returns the underlying DTrace ConsumerEvent. */ public ConsumerEvent getConsumerEvent() { return ce; } /** Returns the Consumer object. */ public Consumer getConsumer() { return ce.getSource(); } public void write(ObjectOutput out) throws IOException { out.writeObject(ce); } public void read(ObjectInput in) throws ClassNotFoundException, IOException { ce = (ConsumerEvent) in.readObject(); } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDataCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.List; import org.openjdk.btrace.core.comm.MessageCommand; import org.opensolaris.os.dtrace.DataEvent; import org.opensolaris.os.dtrace.ProbeData; import org.opensolaris.os.dtrace.Record; /** * Command to represent data event from DTrace. * * @author A. Sundararajan */ public class DTraceDataCommand extends MessageCommand implements DTraceCommand { private DataEvent de; public DTraceDataCommand(DataEvent de) { super(asString(de), true); this.de = de; } /** Returns the underlying DTrace DataEvent. */ public DataEvent getDataEvent() { return de; } public void write(ObjectOutput out) throws IOException { super.write(out); out.writeObject(de); } public void read(ObjectInput in) throws ClassNotFoundException, IOException { super.read(in); de = (DataEvent) in.readObject(); } private static String asString(DataEvent de) { ProbeData pd = de.getProbeData(); List records = pd.getRecords(); StringBuilder buf = new StringBuilder(); for (Record rec : records) { buf.append(rec); } return buf.toString(); } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDropCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.openjdk.btrace.core.comm.MessageCommand; import org.opensolaris.os.dtrace.DropEvent; /** * Command class that represents DTrace drop event. * * @author A. Sundararajan */ public class DTraceDropCommand extends MessageCommand implements DTraceCommand { private DropEvent de; public DTraceDropCommand(DropEvent de) { super(asString(de), true); this.de = de; } /** Returns the underlying DTrace drop event */ public DropEvent getDropEvent() { return de; } public void write(ObjectOutput out) throws IOException { super.write(out); out.writeObject(out); } public void read(ObjectInput in) throws ClassNotFoundException, IOException { super.read(in); de = (DropEvent) in.readObject(); } private static String asString(DropEvent de) { return de.getDrop().getDefaultMessage(); } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceErrorCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.openjdk.btrace.core.comm.ErrorCommand; import org.opensolaris.os.dtrace.ErrorEvent; /** * Command that represents error message from DTrace. * * @author A. Sundararajan */ public class DTraceErrorCommand extends ErrorCommand implements DTraceCommand { private ErrorEvent ee; public DTraceErrorCommand(Exception exp, ErrorEvent ee) { super(exp); this.ee = ee; } /** Returns the underlying DTrace error event. */ public ErrorEvent getErrorEvent() { return ee; } public void write(ObjectOutput out) throws IOException { super.write(out); out.writeObject(ee); } public void read(ObjectInput in) throws ClassNotFoundException, IOException { super.read(in); ee = (ErrorEvent) in.readObject(); } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceExtension.java ================================================ /* * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import java.io.File; import java.io.IOException; import org.openjdk.btrace.core.comm.Command; import org.openjdk.btrace.core.comm.ErrorCommand; import org.openjdk.btrace.core.comm.MessageCommand; import org.openjdk.btrace.core.extensions.Extension; import org.openjdk.btrace.core.extensions.ExtensionContext; import org.openjdk.btrace.core.extensions.Permission; import org.opensolaris.os.dtrace.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * DTrace extension for BTrace. * *

Provides integration with Solaris DTrace for executing D-scripts from BTrace. * *

Use the following code to obtain an instance: * * Example usage: * *


 * {@literal @}Injected
 * private static DTraceExtension dtrace;
 * 
*/ public final class DTraceExtension extends Extension { private static final Logger log = LoggerFactory.getLogger(DTraceExtension.class); private volatile ExtensionContext ctx; private volatile Consumer consumer; @Override public void initialize(ExtensionContext ctx) { super.initialize(ctx); this.ctx = ctx; } @Override public void close() { Consumer cons = this.consumer; if (cons != null) { try { cons.close(); } catch (Exception e) { log.debug("Error closing DTrace consumer", e); } this.consumer = null; } } /** * Submits a D-script from the given file. * * @param file D-script file to submit * @param args DTrace macro arguments * @throws DTraceException if compilation or execution fails * @throws IOException if file cannot be read */ public void submit(File file, String[] args) throws DTraceException, IOException { Consumer cons = newConsumer(args); cons.compile(file, args); start(cons); } /** * Submits a D-script string. * * @param program D-script as a string * @param args DTrace macro arguments * @throws DTraceException if compilation or execution fails */ public void submit(String program, String[] args) throws DTraceException { Consumer cons = newConsumer(args); cons.compile(program, args); start(cons); } private void start(Consumer cons) throws DTraceException { cons.enable(); cons.go( th -> { send(new ErrorCommand(th)); }); } private Consumer newConsumer(String[] args) throws DTraceException { Consumer cons = new LocalConsumer(); this.consumer = cons; cons.addConsumerListener( new ConsumerAdapter() { @Override public void consumerStarted(ConsumerEvent ce) { send(new DTraceStartCommand(ce)); } @Override public void consumerStopped(ConsumerEvent ce) { Consumer c = ce.getSource(); Aggregate ag = null; try { ag = c.getAggregate(); } catch (DTraceException dexp) { send(new ErrorCommand(dexp)); } StringBuilder buf = new StringBuilder(); if (ag != null) { for (Aggregation agg : ag.asMap().values()) { String name = agg.getName(); if (name != null && name.length() > 0) { buf.append(name); buf.append('\n'); } for (AggregationRecord rec : agg.asMap().values()) { buf.append('\t'); buf.append(rec.getTuple()); buf.append(" "); buf.append(rec.getValue()); buf.append('\n'); } } } String msg = buf.toString(); if (msg.length() > 0) { send(new MessageCommand(msg)); } send(new DTraceStopCommand(ce)); c.close(); } @Override public void dataReceived(DataEvent de) { send(new DTraceDataCommand(de)); } @Override public void dataDropped(DropEvent de) { send(new DTraceDropCommand(de)); } @Override public void errorEncountered(ErrorEvent ee) throws ConsumerException { try { super.errorEncountered(ee); } catch (ConsumerException ce) { send(new DTraceErrorCommand(ce, ee)); throw ce; } } }); // open DTrace Consumer cons.open(); // unused macro arguments are fine cons.setOption(Option.argref, ""); // if no macro arg passed use "" or NULL cons.setOption(Option.defaultargs, ""); // allow empty D-scripts cons.setOption(Option.empty, ""); // be quiet! equivalent to DTrace's -q cons.setOption(Option.quiet, ""); // undefined user land symbols are fine cons.setOption(Option.unodefs, ""); // allow zero matching of probes (needed for late loading) cons.setOption(Option.zdefs, ""); if (args != null && args.length > 0) { try { int pid = Integer.parseInt(args[0]); cons.grabProcess(pid); } catch (NumberFormatException e) { log.debug("First argument '{}' is not a valid PID, skipping process grab", args[0]); } catch (Exception e) { log.warn("Failed to grab process with PID '{}': {}", args[0], e.getMessage()); } } return cons; } private void send(Command cmd) { ExtensionContext c = this.ctx; if (c != null) { c.send(cmd); } } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStartCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import org.opensolaris.os.dtrace.ConsumerEvent; /** * Command to represent consumer start event from DTrace. * * @author A. Sundararajan */ public class DTraceStartCommand extends DTraceConsumerCommand { public DTraceStartCommand(ConsumerEvent ce) { super("dtrace-start", ce); } } ================================================ FILE: btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStopCommand.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.dtrace; import org.opensolaris.os.dtrace.ConsumerEvent; /** * Command to represent consumer start event from DTrace. * * @author A. Sundararajan */ public class DTraceStopCommand extends DTraceConsumerCommand { public DTraceStopCommand(ConsumerEvent ce) { super("dtrace-stop", ce); } } ================================================ FILE: btrace-dtrace/src/main/native/btrace.c ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include "org_openjdk_btrace_core_BTraceRuntime.h" #include /* * Class: org_openjdk_btrace_core_BTraceRuntime * Method: dtraceProbe0 * Signature: (Ljava/lang/String;Ljava/lang/String;II)I */ JNIEXPORT jint JNICALL Java_org_openjdk_btrace_core_BTraceRuntime_dtraceProbe0 (JNIEnv *env, jclass cls, jstring str1, jstring str2, jint i1, jint i2) { int result = 0; const char* cstr1; const char* cstr2; if (str1 != NULL) { cstr1 = (*env)->GetStringUTFChars(env, str1, NULL); if (cstr1 == NULL) return 0; } else { cstr1 = NULL; } if (str2 != NULL) { cstr2 = (*env)->GetStringUTFChars(env, str2, NULL); } else { cstr2 = NULL; } if (cstr2 == NULL) { if (cstr1) (*env)->ReleaseStringUTFChars(env, str1, cstr1); return 0; } DTRACE_PROBE5(btrace, event, cstr1, cstr2, i1, i2, &result); if (cstr1) (*env)->ReleaseStringUTFChars(env, str1, cstr1); if (cstr2) (*env)->ReleaseStringUTFChars(env, str2, cstr2); return result; } ================================================ FILE: btrace-dtrace/src/main/native/btraced.d ================================================ provider btrace { probe event(char* c1, char* c2, int i1, int i2, int* i3); }; ================================================ FILE: btrace-dtrace/src/main/resources/META-INF/services/org.openjdk.btrace.core.extensions.Extension ================================================ org.openjdk.btrace.dtrace.DTraceExtension ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregate.java ================================================ package org.opensolaris.os.dtrace; import java.util.Map; @SuppressWarnings("SameReturnValue") public class Aggregate { public Map asMap() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregation.java ================================================ package org.opensolaris.os.dtrace; import java.util.Map; @SuppressWarnings("SameReturnValue") public class Aggregation { public String getName() { return null; } public Map asMap() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationRecord.java ================================================ package org.opensolaris.os.dtrace; @SuppressWarnings("SameReturnValue") public class AggregationRecord { public Tuple getTuple() { return null; } public AggregationValue getValue() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationValue.java ================================================ package org.opensolaris.os.dtrace; public class AggregationValue {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Consumer.java ================================================ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * ident "%Z%%M% %I% %E% SMI" */ package org.opensolaris.os.dtrace; import java.io.File; public interface Consumer { @SuppressWarnings("EmptyMethod") void open(); @SuppressWarnings("EmptyMethod") void setOption(String argref, String s); @SuppressWarnings("EmptyMethod") void grabProcess(int pid); @SuppressWarnings("EmptyMethod") void close(); @SuppressWarnings({"SameReturnValue", "RedundantThrows"}) Aggregate getAggregate() throws DTraceException; @SuppressWarnings("EmptyMethod") void addConsumerListener(ConsumerListener consumerListener); @SuppressWarnings("EmptyMethod") void go(ExceptionHandler exceptionHandler); @SuppressWarnings("EmptyMethod") void enable(); @SuppressWarnings("EmptyMethod") void compile(File program, String[] args); @SuppressWarnings("EmptyMethod") void compile(String program, String[] args); } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerAdapter.java ================================================ package org.opensolaris.os.dtrace; public abstract class ConsumerAdapter implements ConsumerListener { public abstract void consumerStarted(ConsumerEvent ce); public abstract void consumerStopped(ConsumerEvent ce); public abstract void dataReceived(DataEvent de); public abstract void dataDropped(DropEvent de); public void errorEncountered(ErrorEvent ee) throws ConsumerException {} } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerEvent.java ================================================ package org.opensolaris.os.dtrace; @SuppressWarnings("SameReturnValue") public class ConsumerEvent { public Consumer getSource() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerException.java ================================================ package org.opensolaris.os.dtrace; public class ConsumerException extends Exception {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerListener.java ================================================ package org.opensolaris.os.dtrace; public interface ConsumerListener {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DTraceException.java ================================================ package org.opensolaris.os.dtrace; public class DTraceException extends Exception { public DTraceException() {} public DTraceException(String message) { super(message); } public DTraceException(String message, Throwable cause) { super(message, cause); } public DTraceException(Throwable cause) { super(cause); } public DTraceException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DataEvent.java ================================================ package org.opensolaris.os.dtrace; import java.util.EventObject; @SuppressWarnings("SameReturnValue") public class DataEvent extends EventObject { public DataEvent(Object source) { super(source); } public ProbeData getProbeData() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Drop.java ================================================ package org.opensolaris.os.dtrace; @SuppressWarnings("SameReturnValue") public class Drop { public String getDefaultMessage() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DropEvent.java ================================================ package org.opensolaris.os.dtrace; import java.util.EventObject; @SuppressWarnings("SameReturnValue") public class DropEvent extends EventObject { public DropEvent(Object source) { super(source); } public Drop getDrop() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ErrorEvent.java ================================================ package org.opensolaris.os.dtrace; public class ErrorEvent {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ExceptionHandler.java ================================================ package org.opensolaris.os.dtrace; public interface ExceptionHandler { void handleException(Throwable t); } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/LocalConsumer.java ================================================ package org.opensolaris.os.dtrace; import java.io.File; public class LocalConsumer implements Consumer { @Override public void open() {} @Override public void setOption(String argref, String s) {} @Override public void grabProcess(int pid) {} @Override public void close() {} @SuppressWarnings("RedundantThrows") @Override public Aggregate getAggregate() throws DTraceException { return null; } @Override public void addConsumerListener(ConsumerListener consumerListener) {} @Override public void go(ExceptionHandler exceptionHandler) {} @Override public void enable() {} @Override public void compile(File program, String[] args) {} @Override public void compile(String program, String[] args) {} } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Option.java ================================================ package org.opensolaris.os.dtrace; public class Option { public static String argref; public static String defaultargs; public static String empty; public static String quiet; public static String unodefs; public static String zdefs; } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Probe.java ================================================ package org.opensolaris.os.dtrace; public class Probe {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeData.java ================================================ package org.opensolaris.os.dtrace; import java.util.List; @SuppressWarnings("SameReturnValue") public class ProbeData { public List getRecords() { return null; } } ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeDescription.java ================================================ package org.opensolaris.os.dtrace; public class ProbeDescription {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Program.java ================================================ package org.opensolaris.os.dtrace; public class Program {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Record.java ================================================ package org.opensolaris.os.dtrace; public class Record {} ================================================ FILE: btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Tuple.java ================================================ package org.opensolaris.os.dtrace; public class Tuple {} ================================================ FILE: btrace-ext-cli/build.gradle ================================================ plugins { id 'java' } repositories { mavenCentral() } java { toolchain { languageVersion.set(JavaLanguageVersion.of(11)) } } dependencies { implementation project(':btrace-core') implementation project(':btrace-agent') implementation 'com.googlecode.lanterna:lanterna:3.1.5' testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } test { useJUnitPlatform() } jar { archiveBaseName.set('btrace-ext-cli') archiveVersion.set('') archiveClassifier.set('') manifest { attributes('Main-Class': 'org.openjdk.btrace.extcli.Main') } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/ExtensionInspector.java ================================================ package org.openjdk.btrace.extcli; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.*; import java.util.*; import java.util.stream.Stream; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Collectors; import org.openjdk.btrace.core.extensions.Extension; import org.openjdk.btrace.core.extensions.ExtensionMeta; import org.openjdk.btrace.core.extensions.Permission; final class ExtensionInspector { static ExtensionReport inspect(Path input) throws IOException { if (Files.isDirectory(input)) { Path dir = input; Path api = findFirstDeep(dir, "-api.jar"); Path impl = findFirstDeep(dir, "-impl.jar"); if (api == null || impl == null) { return ExtensionReport.error("Missing api/impl jars under " + dir); } String id = readIdFromJar(api); if (id == null || id.isEmpty()) { String n = dir.getFileName() != null ? dir.getFileName().toString() : dir.toString(); id = stripVersionFromName(n); } return inspectJars(id, api, impl); } else if (input.toString().endsWith(".zip")) { // Derive id from zip file name first; will prefer manifest id if available later String fileName = input.getFileName() != null ? input.getFileName().toString() : input.toString(); String id = stripVersionFromName(fileName.replaceFirst("-extension\\.zip$", "")); try (FileSystem fs = FileSystems.newFileSystem(input, (ClassLoader) null)) { Path root = fs.getPath("/"); Path apiIn = findFirstDeep(root, "-api.jar"); Path implIn = findFirstDeep(root, "-impl.jar"); if (apiIn == null || implIn == null) { return ExtensionReport.error("Missing api/impl jars in zip: " + input); } Path api = Files.createTempFile("btracex-api-", ".jar"); Path impl = Files.createTempFile("btracex-impl-", ".jar"); Files.copy(apiIn, api, StandardCopyOption.REPLACE_EXISTING); Files.copy(implIn, impl, StandardCopyOption.REPLACE_EXISTING); api.toFile().deleteOnExit(); impl.toFile().deleteOnExit(); String manifestId = readIdFromJar(api); if (manifestId != null && !manifestId.isEmpty()) id = manifestId; return inspectJars(id, api, impl); } } else { return ExtensionReport.error("Invalid input: not a directory or zip: " + input); } } private static Path findFirstDeep(Path root, String suffix) throws IOException { try (Stream s = Files.walk(root)) { Optional p = s.filter(pth -> pth.getFileName() != null && pth.getFileName().toString().endsWith(suffix)).findFirst(); return p.orElse(null); } } private static ExtensionReport inspectJars(String id, Path api, Path impl) throws IOException { List metas = loadMetasWithoutInstantiating(api, impl); boolean privileged = false; for (ExtensionMeta m : metas) { for (Permission pperm : m.getRequiredPermissions()) { if (pperm.isPrivileged()) { privileged = true; break; } } if (privileged) break; } Set services = readServices(impl); String version = readVersionFromJar(api); if (version == null || version.isEmpty()) { // Fallback: parse from API jar filename, e.g., --api.jar String n = api.getFileName() != null ? api.getFileName().toString() : api.toString(); version = n.replaceFirst("^[^-]+-", "").replaceFirst("-api\\.jar$", ""); } List requiredPerms = readPermissionsFromManifestOrProps(api); // Also merge any permissions from impl jar metadata (some extensions declare there) List implPerms = readPermissionsFromManifestOrProps(impl); if (!implPerms.isEmpty()) { LinkedHashSet merged = new LinkedHashSet<>(requiredPerms); merged.addAll(implPerms); requiredPerms = new ArrayList<>(merged); } // Also merge any extension-level permissions from package-level @ExtensionDescriptor if (metas != null) { LinkedHashSet merged = new LinkedHashSet<>(requiredPerms); for (ExtensionMeta m : metas) { for (Permission p : m.getRequiredPermissions()) merged.add(p.name()); } requiredPerms = new ArrayList<>(merged); } // Also merge any service-level permissions from @ServiceDescriptor on service interfaces if (services != null && !services.isEmpty()) { try (URLClassLoader cl = new URLClassLoader(new URL[] { api.toUri().toURL(), impl.toUri().toURL() }, ExtensionInspector.class.getClassLoader())) { LinkedHashSet merged = new LinkedHashSet<>(requiredPerms); for (String svc : services) { try { Class sc = Class.forName(svc, false, cl); java.lang.annotation.Annotation sd = sc.getAnnotation(org.openjdk.btrace.core.extensions.ServiceDescriptor.class); if (sd instanceof org.openjdk.btrace.core.extensions.ServiceDescriptor) { for (org.openjdk.btrace.core.extensions.Permission p : ((org.openjdk.btrace.core.extensions.ServiceDescriptor) sd).permissions()) { if (p != null) merged.add(p.name()); } } } catch (Throwable ignore) { } } requiredPerms = new ArrayList<>(merged); } catch (Throwable ignore) { } } // Recompute privileged based on the merged permission names if (!privileged) { for (String n : requiredPerms) { try { Permission p = Permission.valueOf(n.trim().toUpperCase()); if (p.isPrivileged()) { privileged = true; break; } } catch (IllegalArgumentException ignored) { /* skip unknown names */ } } } return ExtensionReport.ok(id, version, privileged, services, metas, requiredPerms); } // Read provider class names from META-INF/services/org.openjdk.btrace.core.extensions.Extension // and load their Class objects without instantiating, then extract metadata. private static List loadMetasWithoutInstantiating(Path apiJar, Path implJar) { List result = new ArrayList<>(); try (JarFile jf = new JarFile(implJar.toFile())) { JarEntry svc = jf.getJarEntry("META-INF/services/" + Extension.class.getName()); if (svc == null) return result; List providers = new ArrayList<>(); try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(jf.getInputStream(svc), java.nio.charset.StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) continue; providers.add(line); } } URL[] urls = new URL[] { apiJar.toUri().toURL(), implJar.toUri().toURL() }; try (URLClassLoader cl = new URLClassLoader(urls, ExtensionInspector.class.getClassLoader())) { for (String cn : providers) { try { Class c = Class.forName(cn, false, cl); if (Extension.class.isAssignableFrom(c)) { @SuppressWarnings("unchecked") Class ec = (Class) c; result.add(ExtensionMeta.from(ec)); } } catch (Throwable t) { // skip faulty provider } } } } catch (Throwable ignored) {} return result; } private static Set readServices(Path implJar) { Set services = new HashSet<>(); try (JarFile jf = new JarFile(implJar.toFile())) { Enumeration en = jf.entries(); while (en.hasMoreElements()) { JarEntry e = en.nextElement(); if (e.getName().startsWith("META-INF/services/") && !e.isDirectory()) { services.add(e.getName().substring("META-INF/services/".length())); } } } catch (IOException ignored) {} return services; } private static String readVersionFromJar(Path apiJar) { try (JarFile jf = new JarFile(apiJar.toFile())) { if (jf.getManifest() != null) { String v = jf.getManifest().getMainAttributes().getValue("Implementation-Version"); if (v != null) return v; v = jf.getManifest().getMainAttributes().getValue("BTrace-Extension-Version"); if (v != null) return v; } } catch (IOException ignored) {} return ""; } private static String readIdFromJar(Path jarPath) { try (JarFile jf = new JarFile(jarPath.toFile())) { Manifest mf = jf.getManifest(); if (mf != null) { String id = mf.getMainAttributes().getValue("BTrace-Extension-Id"); if (id != null && !id.isEmpty()) return id; } JarEntry props = jf.getJarEntry("META-INF/btrace-extension.properties"); if (props != null) { Properties p = new Properties(); try (InputStream is = jf.getInputStream(props)) { p.load(is); } String id = p.getProperty("extension.id", ""); if (!id.isEmpty()) return id; } } catch (IOException ignored) {} return ""; } private static String stripVersionFromName(String name) { if (name == null || name.isEmpty()) return name; // Remove extension suffix if present if (name.endsWith(".zip")) name = name.substring(0, name.length() - 4); // Strip trailing version-like segment: last '-' followed by a digit int idx = name.lastIndexOf('-'); if (idx > 0 && idx + 1 < name.length() && Character.isDigit(name.charAt(idx + 1))) { return name.substring(0, idx); } return name; } private static List readPermissionsFromManifestOrProps(Path jarPath) { List perms = new ArrayList<>(); try (JarFile jf = new JarFile(jarPath.toFile())) { Manifest mf = jf.getManifest(); if (mf != null) { String v = mf.getMainAttributes().getValue("BTrace-Extension-Permissions"); if (v != null && !v.trim().isEmpty()) { for (String part : v.split(",")) { String s = part.trim(); if (!s.isEmpty()) perms.add(s); } return perms; } } JarEntry e = jf.getJarEntry("META-INF/btrace-extension.properties"); if (e != null) { Properties p = new Properties(); try (InputStream is = jf.getInputStream(e)) { p.load(is); } String v = p.getProperty("requires.permissions", ""); if (!v.isEmpty()) { for (String part : v.split(",")) { String s = part.trim(); if (!s.isEmpty()) perms.add(s); } } } } catch (IOException ignored) {} return perms; } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/ExtensionLister.java ================================================ package org.openjdk.btrace.extcli; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.*; final class ExtensionLister { static void list(boolean json) throws IOException { List roots = new ArrayList<>(); String home = System.getenv("BTRACE_HOME"); if (home != null) roots.add(Path.of(home, "extensions")); roots.add(Path.of(System.getProperty("user.home"), ".btrace", "extensions")); String extra = System.getenv("BTRACE_EXT_PATH"); if (extra != null) for (String p : extra.split(File.pathSeparator)) roots.add(Path.of(p)); List items = new ArrayList<>(); for (Path root : roots) { File rf = root.toFile(); if (!rf.exists() || !rf.isDirectory()) continue; File[] dirs = rf.listFiles(File::isDirectory); if (dirs == null) continue; for (File d : dirs) { try { ExtensionReport r = ExtensionInspector.inspect(d.toPath()); if (json) items.add(Map.of( "path", d.getAbsolutePath(), "ok", r.ok, "id", r.id, "privileged", r.privileged )); else System.out.println((r.ok ? r.id : d.getName()) + (r.privileged ? " [PRIV]" : "") + " - " + d.getAbsolutePath()); } catch (IOException e) { if (!json) System.err.println("Failed to inspect " + d + ": " + e.getMessage()); } } } if (json) System.out.println(ExtensionReport.toJson(items)); } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/ExtensionReport.java ================================================ package org.openjdk.btrace.extcli; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.openjdk.btrace.core.extensions.ExtensionMeta; import org.openjdk.btrace.core.extensions.Permission; final class ExtensionReport { final boolean ok; final String message; final String id; final String version; final boolean privileged; final Set services; final List metas; final List requiredPermNames; private ExtensionReport(boolean ok, String message, String id, String version, boolean privileged, Set services, List metas, List requiredPermNames) { this.ok = ok; this.message = message; this.id = id; this.version = version; this.privileged = privileged; this.services = services; this.metas = metas; this.requiredPermNames = requiredPermNames != null ? requiredPermNames : Collections.emptyList(); } static ExtensionReport ok(String id, String version, boolean privileged, Set services, List metas, List requiredPermNames) { return new ExtensionReport(true, "", id, version, privileged, services, metas, requiredPermNames); } static ExtensionReport error(String msg) { return new ExtensionReport(false, msg, "", "", false, Collections.emptySet(), Collections.emptyList(), Collections.emptyList()); } public String toString() { if (!ok) return "ERROR: " + message; LinkedHashSet permNames = new LinkedHashSet<>(requiredPermNames); if (metas != null) { for (ExtensionMeta m : metas) { for (Permission p : m.getRequiredPermissions()) { permNames.add(p.name()); } } } String perms = permNames.stream().sorted().collect(Collectors.joining(",")); return String.join("\n", "Extension: " + id, "Version : " + (version == null ? "" : version), "Privileged: " + privileged, "Required : [" + perms + "]", "Services : " + (services.isEmpty() ? "(none)" : String.join(",", services)) ); } public String toJson() { Map obj = new LinkedHashMap<>(); obj.put("ok", ok); if (!ok) obj.put("error", message); obj.put("id", id); obj.put("version", version); obj.put("privileged", privileged); obj.put("services", new ArrayList<>(services)); LinkedHashSet pset = new LinkedHashSet<>(requiredPermNames); if (metas != null) { for (ExtensionMeta m : metas) { for (Permission p : m.getRequiredPermissions()) { pset.add(p.name()); } } } obj.put("requiredPermissions", new ArrayList<>(pset)); return toJson(obj); } static String toJson(Object o) { if (o == null) return "null"; if (o instanceof String) return '"' + ((String)o).replace("\\", "\\\\").replace("\"", "\\\"") + '"'; if (o instanceof Boolean || o instanceof Number) return String.valueOf(o); if (o instanceof Map) { StringBuilder sb = new StringBuilder(); sb.append("{"); boolean first = true; for (Map.Entry e : ((Map)o).entrySet()) { if (!first) sb.append(','); first = false; sb.append(toJson(String.valueOf(e.getKey()))).append(':').append(toJson(e.getValue())); } return sb.append('}').toString(); } if (o instanceof Iterable) { StringBuilder sb = new StringBuilder(); sb.append('['); boolean first = true; for (Object x : (Iterable)o) { if (!first) sb.append(','); first = false; sb.append(toJson(x)); } return sb.append(']').toString(); } return toJson(String.valueOf(o)); } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/Installer.java ================================================ package org.openjdk.btrace.extcli; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.stream.Stream; import java.util.Map; final class Installer { private Installer() {} static void install(String target, List repos, String id, boolean dryRun) throws Exception { Path zipPath; String derivedId = id; if (isUrl(target)) { if (dryRun) { System.out.println("[DRY-RUN] Would download: " + target); return; } zipPath = downloadToTemp(target); derivedId = derivedId != null ? derivedId : deriveIdFromZipName(zipPath.getFileName() != null ? zipPath.getFileName().toString() : zipPath.toString()); } else if (target.endsWith(".zip") && Files.exists(Path.of(target))) { zipPath = Path.of(target); derivedId = derivedId != null ? derivedId : deriveIdFromZipName(Path.of(target).getFileName().toString()); } else if (target.contains(":")) { // GAV coordinate: group:artifact:version String[] parts = target.split(":"); if (parts.length != 3) throw new IllegalArgumentException("Invalid coordinate. Expected groupId:artifactId:version"); String groupId = parts[0]; String artifactId = parts[1]; String version = parts[2]; if (derivedId == null) derivedId = artifactId; List candidates = new ArrayList<>(); String groupPath = groupId.replace('.', '/'); for (String repo : repos) { String base = repo.endsWith("/") ? repo.substring(0, repo.length()-1) : repo; candidates.add(base + "/" + groupPath + "/" + artifactId + "/" + version + "/" + artifactId + "-" + version + "-extension.zip"); candidates.add(base + "/" + groupPath + "/" + artifactId + "/" + version + "/" + artifactId + "-" + version + ".zip"); } if (dryRun) { System.out.println("[DRY-RUN] Candidate URLs:"); for (String c : candidates) System.out.println(" " + c); return; } zipPath = tryDownloadAny(candidates); if (zipPath == null) throw new IOException("Failed to download extension from provided repositories."); } else { throw new IllegalArgumentException("Unrecognized input: provide a zip path, URL, or group:artifact:version"); } // Validate zip contains -api.jar and -impl.jar, and install if (dryRun) { System.out.println("[DRY-RUN] Would install zip: " + zipPath); return; } installZip(zipPath, derivedId); } private static boolean isUrl(String s) { return s.startsWith("http://") || s.startsWith("https://"); } private static Path downloadToTemp(String url) throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setConnectTimeout(10000); conn.setReadTimeout(20000); conn.setInstanceFollowRedirects(true); int code = conn.getResponseCode(); if (code != 200) throw new IOException("HTTP " + code + " for " + url); Path tmp = Files.createTempFile("btracex-download-", ".zip"); try (InputStream in = new BufferedInputStream(conn.getInputStream())) { Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING); } tmp.toFile().deleteOnExit(); return tmp; } private static Path tryDownloadAny(List urls) { for (String u : urls) { try { return downloadToTemp(u); } catch (IOException ignored) { } } return null; } private static String deriveIdFromZipName(String name) { if (name == null) return "extension"; // --extension.zip or -.zip -> String base = name; if (base.endsWith("-extension.zip")) base = base.substring(0, base.length()-"-extension.zip".length()); else if (base.endsWith(".zip")) base = base.substring(0, base.length()-".zip".length()); int idx = base.lastIndexOf('-'); return idx > 0 ? base.substring(0, idx) : base; } private static void installZip(Path zipPath, String id) throws IOException { if (id == null || id.isEmpty()) id = deriveIdFromZipName(zipPath.getFileName() != null ? zipPath.getFileName().toString() : zipPath.toString()); Path targetRoot = getExtensionsRoot(); Path targetDir = targetRoot.resolve(id); Files.createDirectories(targetDir); try (FileSystem fs = FileSystems.newFileSystem(zipPath, (ClassLoader) null)) { Path root = fs.getPath("/"); // Validate presence of -api.jar and -impl.jar boolean hasApi = false, hasImpl = false; try (Stream s = Files.walk(root)) { for (Path p : (Iterable) s::iterator) { if (p.getFileName() == null) continue; String fn = p.getFileName().toString(); if (fn.endsWith("-api.jar")) hasApi = true; if (fn.endsWith("-impl.jar")) hasImpl = true; } } if (!hasApi || !hasImpl) throw new IOException("Invalid extension zip; missing -api.jar or -impl.jar"); // Copy all contents try (Stream s = Files.walk(root)) { for (Path p : (Iterable) s::iterator) { if (Files.isDirectory(p)) continue; Path rel = root.relativize(p); Path dest = targetDir.resolve(rel.toString()); Files.createDirectories(dest.getParent()); Files.copy(p, dest, StandardCopyOption.REPLACE_EXISTING); } } } System.out.println("Installed extension '" + id + "' into: " + targetDir); try { ExtensionReport rep = ExtensionInspector.inspect(targetDir); System.out.println(rep.toString()); System.out.println(); System.out.println("Hint: To enable/disable this extension for implementations, edit your policy:"); System.out.println(" btracex policy edit --home"); System.out.println("Or set explicitly:"); System.out.println(" btracex policy set --allowExtensions " + id + " --policy-file ~/.btrace/permissions.properties"); if (rep.privileged) { System.out.println(); System.out.println("Note: This extension requires privileged permissions. You can allow all privileged extensions with:"); System.out.println(" btracex policy set --allowPrivileged true --policy-file ~/.btrace/permissions.properties"); } } catch (Exception ignored) { } } private static Path getExtensionsRoot() throws IOException { String home = System.getenv("BTRACE_HOME"); if (home != null && !home.isEmpty()) { Path p = Path.of(home, "extensions"); Files.createDirectories(p); return p; } Path user = Path.of(System.getProperty("user.home"), ".btrace", "extensions"); Files.createDirectories(user); return user; } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/Main.java ================================================ package org.openjdk.btrace.extcli; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import org.openjdk.btrace.extcli.tui.ExtRepoBrowser; public final class Main { public static void main(String[] args) throws Exception { if (args.length == 0 || Arrays.asList("-h", "--help").contains(args[0])) { usage(); return; } String cmd = args[0]; switch (cmd) { case "inspect": if (args.length < 2) { // No argument: open interactive TUI in-process ExtRepoBrowser.launch(Arrays.copyOfRange(args, 1, args.length)); break; } boolean json = hasFlag(args, "--json"); String arg = args[1]; ExtensionReport report; Path p = Paths.get(arg); if (Files.exists(p)) { report = ExtensionInspector.inspect(p); } else { Path resolved = RepoScanner.resolveById(arg); if (resolved == null) { err("Extension id not found in known repositories: " + arg); return; } report = ExtensionInspector.inspect(resolved); } if (json) System.out.println(report.toJson()); else System.out.println(report); break; case "list": boolean jsonList = hasFlag(args, "--json"); ExtensionLister.list(jsonList); break; case "policy": runPolicy(Arrays.copyOfRange(args, 1, args.length)); break; case "install": if (args.length < 2) { err("Missing coordinate or path. Usage: btracex install [--repo ...] [--id ] [--dry-run]"); System.exit(2); } runInstall(Arrays.copyOfRange(args, 1, args.length)); break; default: err("Unknown command: " + cmd); usage(); } } private static void runInstall(String[] args) throws Exception { String target = null; List repos = new ArrayList<>(); String id = null; boolean dryRun = false; for (int i = 0; i < args.length; i++) { String a = args[i]; if (a.equals("--repo") && i + 1 < args.length) { repos.add(args[++i]); } else if (a.equals("--id") && i + 1 < args.length) { id = args[++i]; } else if (a.equals("--dry-run")) { dryRun = true; } else if (a.startsWith("--")) { err("Unknown option: " + a); usage(); return; } else if (target == null) { target = a; } else { err("Unexpected argument: " + a); usage(); return; } } if (repos.isEmpty()) { repos.add("https://repo1.maven.org/maven2"); } if (target == null) { err("Missing coordinate or path"); usage(); return; } Installer.install(target, repos, id, dryRun); } private static void runPolicy(String[] args) throws IOException { if (args.length == 0) { err("policy requires a subcommand: print|set|edit"); usage(); return; } String sub = args[0]; PolicyFile target = PolicyFile.fromArgs(Arrays.copyOfRange(args, 1, args.length)); switch (sub) { case "print": System.out.println(target.describe(hasFlag(args, "--json"))); break; case "set": target.updateFromArgs(Arrays.copyOfRange(args, 1, args.length)); target.save(); System.out.println("Policy saved to " + target.getTarget()); break; case "edit": System.err.println("Interactive editor not yet implemented; use 'btracex policy set' for now."); System.exit(2); break; default: err("Unknown policy subcommand: " + sub); usage(); } } private static boolean hasFlag(String[] args, String flag) { for (String a : args) if (flag.equals(a)) return true; return false; } private static void usage() { System.out.println("btracex - BTrace extensions CLI\n" + "Usage:\n" + " btracex inspect [] [--json] # no args opens repo browser\n" + " btracex list [--json]\n" + " btracex policy print [--policy-file |--home|--classpath ] [--json]\n" + " btracex policy set [--allowExtensions ] [--denyExtensions ] [--allowPrivileged ] [--policy-file |--home|--classpath ]\n" + " btracex install [--repo ...] [--id ] [--dry-run]\n"); } private static void err(String s) { System.err.println(s); } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/PolicyFile.java ================================================ package org.openjdk.btrace.extcli; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.nio.file.StandardCopyOption; final class PolicyFile { private Path target; private final Properties props = new Properties(); static PolicyFile fromArgs(String[] args) throws IOException { Path t = null; Path classpathDir = null; boolean home = false; for (int i = 0; i < args.length; i++) { switch (args[i]) { case "--policy-file": t = Path.of(args[++i]); break; case "--home": home = true; break; case "--classpath": classpathDir = Path.of(args[++i]); break; } } if (t != null && (home || classpathDir != null)) throw new IllegalArgumentException("Use only one of --policy-file, --home, --classpath"); PolicyFile pf = new PolicyFile(); if (t != null) pf.target = t; else if (home) pf.target = Path.of(System.getProperty("user.home"), ".btrace", "permissions.properties"); else if (classpathDir != null) pf.target = classpathDir.resolve("META-INF/btrace/permissions.properties"); else pf.target = Path.of(System.getProperty("user.home"), ".btrace", "permissions.properties"); pf.loadIfExists(); return pf; } private void loadIfExists() throws IOException { if (Files.exists(target)) try (InputStream is = Files.newInputStream(target)) { props.load(is); } } void updateFromArgs(String[] args) { for (int i = 0; i < args.length; i++) { switch (args[i]) { case "--allowExtensions": props.setProperty("allowExtensions", args[++i]); break; case "--denyExtensions": props.setProperty("denyExtensions", args[++i]); break; case "--allowPrivileged": props.setProperty("allowPrivileged", args[++i]); break; } } } String describe(boolean json) { Map m = Map.of( "target", String.valueOf(target), "allowExtensions", props.getProperty("allowExtensions", ""), "denyExtensions", props.getProperty("denyExtensions", ""), "allowPrivileged", props.getProperty("allowPrivileged", "false") ); if (json) return toJson(m); return String.format("Policy: %s\n allowExtensions=%s\n denyExtensions=%s\n allowPrivileged=%s", target, m.get("allowExtensions"), m.get("denyExtensions"), m.get("allowPrivileged")); } void save() throws IOException { Files.createDirectories(target.getParent()); Path tmp = target.resolveSibling(target.getFileName() + ".tmp"); try (OutputStream os = Files.newOutputStream(tmp)) { props.store(os, "BTrace permissions policy"); } Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } Path getTarget() { return target; } static String toJson(Object o) { return ExtensionReport.toJson(o); } List getAllowList() { return splitCsv(props.getProperty("allowExtensions", "")); } List getDenyList() { return splitCsv(props.getProperty("denyExtensions", "")); } boolean isAllowed(String id) { return getAllowList().contains(id); } boolean isDenied(String id) { return getDenyList().contains(id); } void allow(String id) { List allow = new ArrayList<>(getAllowList()); if (!allow.contains(id)) allow.add(id); props.setProperty("allowExtensions", String.join(",", allow)); // remove from deny if present List deny = new ArrayList<>(getDenyList()); deny.remove(id); props.setProperty("denyExtensions", String.join(",", deny)); } void deny(String id) { List deny = new ArrayList<>(getDenyList()); if (!deny.contains(id)) deny.add(id); props.setProperty("denyExtensions", String.join(",", deny)); // remove from allow if present List allow = new ArrayList<>(getAllowList()); allow.remove(id); props.setProperty("allowExtensions", String.join(",", allow)); } void clear(String id) { List allow = new ArrayList<>(getAllowList()); allow.remove(id); props.setProperty("allowExtensions", String.join(",", allow)); List deny = new ArrayList<>(getDenyList()); deny.remove(id); props.setProperty("denyExtensions", String.join(",", deny)); } String headerSummary() { String allowPriv = props.getProperty("allowPrivileged", "false"); return String.format("Policy: %s | allowExtensions=%s | denyExtensions=%s | allowPrivileged=%s", String.valueOf(target), props.getProperty("allowExtensions", ""), props.getProperty("denyExtensions", ""), allowPriv); } private static List splitCsv(String csv) { List res = new ArrayList<>(); if (csv == null || csv.trim().isEmpty()) return res; for (String s : csv.split(",")) { String t = s.trim(); if (!t.isEmpty()) res.add(t); } return res; } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/RepoBrowser.java ================================================ package org.openjdk.btrace.extcli; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; final class RepoBrowser { static void browse() throws IOException { browse(new String[0]); } static void browse(String[] args) throws IOException { List items = new ArrayList<>(); for (Path root : RepoScanner.roots()) { if (!Files.isDirectory(root)) continue; try (Stream s = Files.list(root)) { for (Path p : (Iterable) s::iterator) { if (Files.isDirectory(p)) items.add(p); } } } if (items.isEmpty()) { System.out.println("No extensions found in known repositories."); return; } // Load or create target policy (defaults to home) PolicyFile policy; try { policy = PolicyFile.fromArgs(args); } catch (Exception e) { System.out.println("Failed to load policy: " + e.getMessage()); return; } BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int sel = 0; while (true) { clearScreen(); System.out.println(policy.headerSummary()); System.out.println("Use Up/Down (or j/k) to move, 'e' to enable/disable, 'i' for details, 'q' to quit."); System.out.println(); for (int i = 0; i < items.size(); i++) { Path dir = items.get(i); String id = dir.getFileName() != null ? dir.getFileName().toString() : String.valueOf(dir); String ver = ""; try { ExtensionReport r = ExtensionInspector.inspect(dir); if (r != null) { if (r.id != null && !r.id.isEmpty()) id = r.id; if (r.version != null) ver = r.version; } } catch (Exception ignored) { } String verTag = (ver != null && !ver.isEmpty()) ? ("[" + ver + "]") : ""; String state = policy.isAllowed(id) ? "[A]" : (policy.isDenied(id) ? "[D]" : "[ ]"); String cursor = (i == sel) ? "->" : " "; System.out.printf("%s %s %s%s (%s)%n", cursor, state, id, verTag, dir); } System.out.print("> "); String line = br.readLine(); if (line == null) return; if (isUp(line)) { sel = (sel + items.size() - 1) % items.size(); continue; } if (isDown(line)) { sel = (sel + 1) % items.size(); continue; } line = line.trim(); if (line.equalsIgnoreCase("q")) return; if (line.equalsIgnoreCase("i")) { showDetails(items.get(sel), br); continue; } if (line.equalsIgnoreCase("e")) { Path dir = items.get(sel); String id = dir.getFileName() != null ? dir.getFileName().toString() : String.valueOf(dir); try { ExtensionReport r = ExtensionInspector.inspect(dir); if (r != null && r.id != null && !r.id.isEmpty()) id = r.id; boolean currentlyAllowed = policy.isAllowed(id); boolean currentlyDenied = policy.isDenied(id); if (currentlyAllowed) { // disable => move to deny policy.deny(id); policy.save(); } else if (currentlyDenied) { // enable => move to allow if (confirmEnable(br, r)) { policy.allow(id); policy.save(); } } else { // default => ask to enable if (confirmEnable(br, r)) { policy.allow(id); policy.save(); } } } catch (Exception e) { // ignore } continue; } // If number entered, jump to index try { int idx = Integer.parseInt(line); if (idx >= 0 && idx < items.size()) sel = idx; } catch (NumberFormatException ignored) { } } } private static boolean isUp(String line) { return line.contains("\u001B[A") || line.equalsIgnoreCase("k"); } private static boolean isDown(String line) { return line.contains("\u001B[B") || line.equalsIgnoreCase("j"); } private static void showDetails(Path dir, BufferedReader br) throws IOException { clearScreen(); try { ExtensionReport rep = ExtensionInspector.inspect(dir); System.out.println(rep.toString()); } catch (Exception e) { System.out.println("Failed to inspect: " + e.getMessage()); } System.out.println(); System.out.println("(Press Enter to continue)"); br.readLine(); } private static boolean confirmEnable(BufferedReader br, ExtensionReport rep) throws IOException { clearScreen(); if (rep != null) System.out.println(rep.toString()); System.out.println(); System.out.print("Enable this extension? [y/N]: "); String ans = br.readLine(); return ans != null && (ans.equalsIgnoreCase("y") || ans.equalsIgnoreCase("yes")); } private static void clearScreen() { // Best-effort clear; will just print newlines if ANSI unsupported System.out.print("\033[H\033[2J"); System.out.flush(); } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/RepoScanner.java ================================================ package org.openjdk.btrace.extcli; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; final class RepoScanner { static List roots() { List roots = new ArrayList<>(); String home = System.getenv("BTRACE_HOME"); if (home != null) roots.add(Path.of(home, "extensions")); roots.add(Path.of(System.getProperty("user.home"), ".btrace", "extensions")); String extra = System.getenv("BTRACE_EXT_PATH"); if (extra != null) for (String p : extra.split(File.pathSeparator)) roots.add(Path.of(p)); return roots; } static Path resolveById(String id) { for (Path root : roots()) { Path dir = root.resolve(id); if (dir.toFile().isDirectory()) return dir; } return null; } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/tui/ExtRepoBrowser.java ================================================ package org.openjdk.btrace.extcli.tui; // Copied from client.tui and adapted to ext-cli package import com.googlecode.lanterna.SGR; import com.googlecode.lanterna.TerminalSize; import com.googlecode.lanterna.TextColor; import com.googlecode.lanterna.gui2.*; import com.googlecode.lanterna.gui2.table.DefaultTableRenderer; import com.googlecode.lanterna.gui2.table.Table; import com.googlecode.lanterna.gui2.table.TableCellRenderer; import com.googlecode.lanterna.gui2.table.TableHeaderRenderer; import com.googlecode.lanterna.gui2.dialogs.ListSelectDialogBuilder; import com.googlecode.lanterna.gui2.dialogs.MessageDialog; import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder; import com.googlecode.lanterna.input.KeyStroke; import com.googlecode.lanterna.input.KeyType; import com.googlecode.lanterna.gui2.TextGUIGraphics; import com.googlecode.lanterna.screen.Screen; import com.googlecode.lanterna.terminal.DefaultTerminalFactory; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.openjdk.btrace.core.extensions.Permission; import java.util.stream.Stream; public final class ExtRepoBrowser { private ExtRepoBrowser() {} private enum SortKey { STATE, ID, VERSION, PATH } private static final boolean DEBUG_STATE_RENDER = false; public static void launch(String[] args) throws Exception { DefaultTerminalFactory factory = new DefaultTerminalFactory(); Screen screen = factory.createScreen(); screen.startScreen(); MultiWindowTextGUI gui = new MultiWindowTextGUI(screen, new DefaultWindowManager(), new EmptySpace()); final Window window = new BasicWindow("BTrace Extensions"); window.setHints(List.of(Window.Hint.FULL_SCREEN)); Panel root = new Panel(new BorderLayout()); Panel headerPanel = new Panel(new LinearLayout(Direction.VERTICAL)); headerPanel.setLayoutData(BorderLayout.Location.TOP); Label headerPolicy = new Label(""); headerPolicy.setForegroundColor(TextColor.ANSI.WHITE); headerPolicy.setBackgroundColor(TextColor.ANSI.BLUE); Label headerRepos = new Label(""); headerRepos.setForegroundColor(TextColor.ANSI.WHITE); headerRepos.setBackgroundColor(TextColor.ANSI.BLUE); headerPanel.addComponent(headerPolicy); headerPanel.addComponent(headerRepos); root.addComponent(headerPanel); Panel center = new Panel(new LinearLayout(Direction.VERTICAL)); PolicyFileLite policy = PolicyFileLite.homeDefault(); List all = new ArrayList<>(); TuiState state = TuiState.load(); final String[] filterText = new String[] { state.filter == null ? "" : state.filter }; final SortKey[] sortKey = new SortKey[] { parseSortKey(state.sortKey) }; final boolean[] sortAsc = new boolean[] { state.sortAsc }; final boolean[] scanning = new boolean[] { false }; final double[] splitRatio = new double[] { clampRatio(state.splitRatio) }; final PolicyFileLite[] polRef = new PolicyFileLite[] { policy }; final Label[] detailsRef = new Label[1]; final Label[] footerRef = new Label[1]; final Runnable[] refreshRef = new Runnable[1]; final boolean[] splitAdjustMode = new boolean[] { false }; Table table = new Table("State", "Id", "Version") { @Override public Interactable.Result handleKeyStroke(KeyStroke keyStroke) { if (splitAdjustMode[0]) { if (keyStroke.getKeyType() == KeyType.ArrowUp) { splitRatio[0] = Math.max(0.3, splitRatio[0] - 0.05); saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); recomputeSizes(window.getTextGUI().getScreen(), this, detailsRef[0], splitRatio); try { this.invalidate(); if (detailsRef[0] != null) detailsRef[0].invalidate(); window.invalidate(); } catch (Throwable ignore) {} return Interactable.Result.HANDLED; } else if (keyStroke.getKeyType() == KeyType.ArrowDown) { splitRatio[0] = Math.min(0.9, splitRatio[0] + 0.05); saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); recomputeSizes(window.getTextGUI().getScreen(), this, detailsRef[0], splitRatio); try { this.invalidate(); if (detailsRef[0] != null) detailsRef[0].invalidate(); window.invalidate(); } catch (Throwable ignore) {} return Interactable.Result.HANDLED; } } if (keyStroke.isShiftDown()) { if (keyStroke.getKeyType() == KeyType.ArrowUp) { splitRatio[0] = Math.max(0.3, splitRatio[0] - 0.05); saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); recomputeSizes(window.getTextGUI().getScreen(), this, detailsRef[0], splitRatio); return Interactable.Result.HANDLED; } else if (keyStroke.getKeyType() == KeyType.ArrowDown) { splitRatio[0] = Math.min(0.9, splitRatio[0] + 0.05); saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); recomputeSizes(window.getTextGUI().getScreen(), this, detailsRef[0], splitRatio); return Interactable.Result.HANDLED; } } if (!splitAdjustMode[0] && (keyStroke.getKeyType() == KeyType.ArrowUp || keyStroke.getKeyType() == KeyType.ArrowDown || keyStroke.getKeyType() == KeyType.PageUp || keyStroke.getKeyType() == KeyType.PageDown || keyStroke.getKeyType() == KeyType.Home || keyStroke.getKeyType() == KeyType.End)) { Interactable.Result r = super.handleKeyStroke(keyStroke); Item it = findItemByRow(all, this, getSelectedRow()); if (it != null && detailsRef[0] != null) { detailsRef[0].setText(describe(it, polRef[0])); detailsRef[0].setForegroundColor(it.privileged ? TextColor.ANSI.YELLOW : TextColor.ANSI.DEFAULT); } return r; } if (keyStroke.getKeyType() == KeyType.Character) { char c = keyStroke.getCharacter(); if (c == ' ' || c == 'c' || c == 'C') { Item it = findItemByRow(all, this, getSelectedRow()); if (it != null) { try { if (c == ' ') { if (polRef[0].isAllowed(it.id)) polRef[0].deny(it.id); else if (polRef[0].isDenied(it.id)) polRef[0].allow(it.id); else { if (!confirmAllow((WindowBasedTextGUI)window.getTextGUI(), it)) return Interactable.Result.HANDLED; polRef[0].allow(it.id); } } else { polRef[0].clear(it.id); } polRef[0].save(); } catch (Exception e) { MessageDialog.showMessageDialog((WindowBasedTextGUI)window.getTextGUI(), "Error", "Failed to update policy: " + e.getMessage()); } if (refreshRef[0] != null) refreshRef[0].run(); if (footerRef[0] != null) showToast((WindowBasedTextGUI)window.getTextGUI(), footerRef[0], scanning, () -> "Arrows navigate • space toggle • c clear • e explain • / filter • s sort • m adjust-split • ? help • q quit", "Saved", 900); } return Interactable.Result.HANDLED; } } return super.handleKeyStroke(keyStroke); } }; Border tableBox = Borders.singleLine("Extensions"); ((AbstractBorder)tableBox).setComponent(table); center.addComponent(tableBox); Label legend = new Label("State: ?=default, +=allowed, -=denied"); center.addComponent(legend); center.addComponent(new Separator(Direction.HORIZONTAL)); Label details = new Label(""); Border detailsBox = Borders.singleLine("Details"); ((AbstractBorder)detailsBox).setComponent(details); center.addComponent(detailsBox); detailsRef[0] = details; root.addComponent(center, BorderLayout.Location.CENTER); Label footer = new Label("Arrows navigate • space toggle • c clear • e explain • / filter • s sort • m adjust-split • ? help • q quit"); footer.setForegroundColor(TextColor.ANSI.BLACK); footer.setBackgroundColor(TextColor.ANSI.WHITE); root.addComponent(footer, BorderLayout.Location.BOTTOM); footerRef[0] = footer; window.setComponent(root); Runnable refresh = () -> { table.getTableModel().clear(); String f = filterText[0].toLowerCase(Locale.ROOT); Comparator cmp = comparator(sortKey[0], sortAsc[0], policy); new ArrayList<>(all).stream() .filter(it -> f.isEmpty() || it.id.toLowerCase(Locale.ROOT).contains(f) || it.dir.toString().toLowerCase(Locale.ROOT).contains(f)) .sorted(cmp) .forEach(it -> table.getTableModel().addRow(stateSymbol(policy, it), it.id, it.version == null ? "" : it.version)); headerPolicy.setText(buildPolicyHeader(policy)); headerRepos.setText(buildReposHeader()); footer.setText(scanning[0] ? ("Scanning extensions… (" + all.size() + ") • Press q to quit") : (splitAdjustMode[0] ? "Adjust split: Up/Down to resize • Esc to exit" : "Arrows navigate • space toggle • c clear • e explain • / filter • s sort • m adjust-split • ? help • q quit")); int sel = table.getSelectedRow(); if (sel >= 0 && sel < table.getTableModel().getRowCount()) { Item it = findItemByRow(all, table, sel); if (it != null) { details.setText(describe(it, policy)); details.setForegroundColor(it.privileged ? TextColor.ANSI.YELLOW : TextColor.ANSI.DEFAULT); } } else { details.setText(""); } }; refreshRef[0] = refresh; try { DefaultTableRenderer dr = (DefaultTableRenderer) table.getRenderer(); dr.setAllowPartialColumn(false); Set expand = new HashSet<>(); expand.add(1); dr.setExpandableColumns(expand); } catch (Throwable ignored) {} table.setTableHeaderRenderer(new TableHeaderRenderer() { @Override public TerminalSize getPreferredSize(Table table, String label, int columnIndex) { int pad = 2; if (columnIndex == 0) return new TerminalSize(Math.max(3 + pad * 2, (label == null ? 0 : label.length()) + pad * 2), 1); return new TerminalSize((label == null ? 0 : label.length()) + pad * 2, 1); } @Override public void drawHeader(Table table, String label, int columnIndex, TextGUIGraphics g) { String base = label == null ? "" : label; g.setBackgroundColor(TextColor.ANSI.WHITE); g.setForegroundColor(TextColor.ANSI.BLACK); g.fill(' '); TerminalSize sz = g.getSize(); int width = sz != null ? sz.getColumns() : base.length(); int x = Math.max(0, (width - base.length()) / 2); g.enableModifiers(SGR.BOLD); g.putString(x, 0, base); g.disableModifiers(SGR.BOLD); } }); table.setTableCellRenderer(new TableCellRenderer() { @Override public TerminalSize getPreferredSize(Table table, String cell, int column, int row) { int pad = 2; if (column == 0) return new TerminalSize(3 + pad * 2, 1); int len = (cell == null ? 0 : cell.length()) + pad * 2; return new TerminalSize(len, 1); } @Override public void drawCell(Table table, String cell, int column, int row, TextGUIGraphics graphics) { boolean isSelected = row == table.getSelectedRow(); if (isSelected) { graphics.setBackgroundColor(TextColor.ANSI.CYAN); } String text = cell == null ? "" : cell; if (column == 0) { try { String id = table.getTableModel().getRow(row).get(1); boolean allowed = polRef[0].isAllowed(id); boolean denied = polRef[0].isDenied(id); String sym = allowed ? "+" : (denied ? "-" : "?"); if (DEBUG_STATE_RENDER) { text = "[" + sym + "]"; try { System.err.println("[TUI-STATE] row=" + row + " id=" + id + " allowed=" + allowed + " denied=" + denied + " sym=" + sym); } catch (Throwable ignore) {} } else { text = sym; } } catch (Exception ignored) {} if (text == null || text.isEmpty() || " ".equals(text)) text = "?"; } graphics.setForegroundColor(TextColor.ANSI.BLACK); TerminalSize sz = graphics.getSize(); graphics.fill(' '); String padded = " " + text + " "; graphics.putString(0, 0, padded); } }); window.addWindowListener(new WindowListenerAdapter() { @Override public void onUnhandledInput(Window basePane, KeyStroke keyStroke, AtomicBoolean handled) { switch (keyStroke.getKeyType()) { case Character: { char c = keyStroke.getCharacter(); if (c == 'q' || c == 'Q') { saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); window.close(); handled.set(true); return; } if (c == '?') { showHelp(gui); handled.set(true); return; } if (c == 'm' || c == 'M') { if (splitAdjustMode[0]) { splitAdjustMode[0] = false; footer.setText("Arrows navigate • space toggle • c clear • e explain • / filter • s sort • m adjust-split • ? help • q quit"); } else { splitAdjustMode[0] = true; footer.setText("Adjust split: Up/Down to resize • Esc to exit"); } handled.set(true); return; } if (c == 'e' || c == 'E') { Item it = findItemByRow(all, table, table.getSelectedRow()); if (it != null) showPrivileges(gui, it); handled.set(true); return; } if (c == '/') { String ft = new TextInputDialogBuilder().setTitle("Filter").setDescription("Enter filter (id/path contains):").build().showDialog(gui); if (ft != null) { filterText[0] = ft; saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); refresh.run(); } handled.set(true); return; } if (c == 's' || c == 'S') { SortKey chosen = chooseSort(gui, sortKey[0]); if (chosen != null) { if (chosen == sortKey[0]) sortAsc[0] = !sortAsc[0]; else { sortKey[0] = chosen; sortAsc[0] = true; } saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); refresh.run(); } handled.set(true); return; } if (c == 'r' || c == 'R') { scanAsync(gui, all, refresh, scanning); handled.set(true); return; } break; } case Escape: { if (splitAdjustMode[0]) { splitAdjustMode[0] = false; footer.setText("Arrows navigate • space toggle • c clear • e explain • / filter • s sort • m adjust-split • ? help • q quit"); handled.set(true); return; } break; } case ArrowUp: case ArrowDown: { if (splitAdjustMode[0]) { if (keyStroke.getKeyType() == KeyType.ArrowUp) splitRatio[0] = Math.max(0.3, splitRatio[0] - 0.05); else splitRatio[0] = Math.min(0.9, splitRatio[0] + 0.05); saveState(filterText[0], sortKey[0], sortAsc[0], splitRatio[0]); recomputeSizes(window.getTextGUI().getScreen(), table, details, splitRatio); try { table.invalidate(); details.invalidate(); window.invalidate(); } catch (Throwable ignore) {} handled.set(true); return; } break; } default: break; } } @Override public void onResized(Window window, TerminalSize oldSize, TerminalSize newSize) { recomputeSizes(window.getTextGUI().getScreen(), table, details, splitRatio); } }); recomputeSizes(screen, table, details, splitRatio); scanAsync(gui, all, refresh, scanning); window.setComponent(root); gui.addWindowAndWait(window); screen.stopScreen(); } private static String stateSymbol(PolicyFileLite policy, Item it) { if (policy.isAllowed(it.id)) return "+"; if (policy.isDenied(it.id)) return "-"; return "?"; } private static Comparator comparator(SortKey k, boolean asc, PolicyFileLite policy) { Comparator c; switch (k) { case STATE: c = Comparator.comparing((Item i) -> stateOrder(policy, i)); break; case VERSION: c = Comparator.comparing(i -> i.version == null ? "" : i.version, String::compareToIgnoreCase); break; case PATH: c = Comparator.comparing(i -> i.dir.toString(), String::compareToIgnoreCase); break; case ID: default: c = Comparator.comparing(i -> i.id, String::compareToIgnoreCase); break; } return asc ? c : c.reversed(); } private static int stateOrder(PolicyFileLite policy, Item it) { if (policy.isAllowed(it.id)) return 0; if (policy.isDenied(it.id)) return 2; return 1; } private static SortKey parseSortKey(String key) { if (key == null) return SortKey.ID; try { return SortKey.valueOf(key.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException ex) { return SortKey.ID; } } private static double clampRatio(double r) { if (Double.isNaN(r) || Double.isInfinite(r)) return 0.65d; if (r < 0.3d) return 0.3d; if (r > 0.9d) return 0.9d; return r; } private static void saveState(String filter, SortKey sortKey, boolean sortAsc, double ratio) { TuiState s = new TuiState(); s.filter = filter == null ? "" : filter; s.sortKey = sortKey == null ? "ID" : sortKey.name(); s.sortAsc = sortAsc; s.splitRatio = clampRatio(ratio); TuiState.save(s); } private static SortKey chooseSort(MultiWindowTextGUI gui, SortKey current) { List opts = Arrays.asList(SortKey.STATE, SortKey.ID, SortKey.VERSION, SortKey.PATH); return new ListSelectDialogBuilder() .setTitle("Sort") .setDescription("Sort by (repeat to toggle asc/desc):") .addListItems(opts.toArray(new SortKey[0])) .build() .showDialog(gui); } private static void showHelp(MultiWindowTextGUI gui) { MessageDialog.showMessageDialog(gui, "Help", "Keys:\n" + " Arrows/PageUp/PageDown/Home/End: navigate (details update automatically)\n" + " space: Toggle state in list (+/-/?)\n" + " c: Clear to default (removes from allow/deny)\n" + " e: Explain privileges (dialog with risk descriptions)\n" + " /: Filter s: Sort m: Adjust split (then Up/Down, Esc to exit)\n" + "\n" + "Details panel:\n" + " Shows full-word state: default / allowed / denied\n" + " ?: Help q: Quit\n"); } private static boolean confirmAllow(WindowBasedTextGUI gui, Item it) { StringBuilder sb = new StringBuilder(); sb.append("Allow extension '" + it.id + "'?"); List names = it.requiredPerms; if (names != null && !names.isEmpty()) { sb.append("\n\nRequests permissions:\n"); for (String n : names) { String up = n.trim().toUpperCase(Locale.ROOT); String desc; try { Permission p = Permission.valueOf(up); desc = p.getRiskDescription(); } catch (IllegalArgumentException ex) { desc = "(unknown)"; } sb.append(" - ").append(up).append(": ").append(desc).append('\n'); } } else { sb.append("\n\nThis extension does not declare specific permissions."); } MessageDialogButton res = MessageDialog.showMessageDialog(gui, "Confirm Allow", sb.toString(), MessageDialogButton.Yes, MessageDialogButton.No); return res == MessageDialogButton.Yes; } private static void showToast(WindowBasedTextGUI gui, Label footer, boolean[] scanning, Supplier base, String text, int millis) { String old = footer.getText(); footer.setText(text); new Thread(() -> { try { Thread.sleep(millis); } catch (InterruptedException ignored) {} gui.getGUIThread().invokeLater(() -> footer.setText(scanning[0] ? ("Scanning extensions… • Press q to quit") : base.get())); }, "btracex-toast").start(); } private static void showPrivileges(MultiWindowTextGUI gui, Item it) { StringBuilder sb = new StringBuilder(); sb.append("Extension: ").append(it.id).append('\n'); List names = it.requiredPerms; if (names != null && !names.isEmpty()) { sb.append("\nRequested permissions:\n"); for (String n : names) { String up = n.trim().toUpperCase(Locale.ROOT); String desc; try { Permission p = Permission.valueOf(up); desc = p.getRiskDescription(); } catch (IllegalArgumentException ex) { desc = "(unknown)"; } sb.append(" - ").append(up).append(": ").append(desc).append('\n'); } } else { sb.append("\nNo explicit permissions declared by this extension."); } MessageDialog.showMessageDialog(gui, "Privileges", sb.toString(), MessageDialogButton.OK); } private static String describe(Item it, PolicyFileLite policy) { String state = policy.isAllowed(it.id) ? "allowed" : (policy.isDenied(it.id) ? "denied" : "default"); StringBuilder sb = new StringBuilder(); sb.append("Extension: ").append(it.id) .append("\nVersion : ").append(it.version == null ? "" : it.version) .append("\nPrivileged: ").append(it.privileged) .append("\nState : ").append(state) .append("\nPath : ").append(it.dir); if (it.requiredPerms != null && !it.requiredPerms.isEmpty()) { sb.append("\nRequires : "); for (int i = 0; i < it.requiredPerms.size(); i++) { if (i > 0) sb.append(','); sb.append(it.requiredPerms.get(i)); } } return sb.toString(); } private static Item findItemByRow(List all, Table table, int row) { if (row < 0 || row >= table.getTableModel().getRowCount()) return null; String id = table.getTableModel().getRow(row).get(1); for (Item it : all) if (it.id.equals(id)) return it; return null; } private static void recomputeSizes(Screen screen, Table table, Label details, double[] splitRatio) { TerminalSize ts = screen.getTerminalSize(); int totalCols = Math.max(80, ts.getColumns()); int totalRows = Math.max(24, ts.getRows()); int headerFooter = 2; int centerRows = Math.max(10, totalRows - headerFooter); int reserved = 2; int tableRows = Math.max(6, (int)Math.round(centerRows * splitRatio[0]) - reserved); int detailsRows = Math.max(4, centerRows - tableRows - reserved); table.setPreferredSize(new TerminalSize(totalCols, tableRows)); details.setPreferredSize(new TerminalSize(totalCols, detailsRows)); } private static String buildPolicyHeader(PolicyFileLite policy) { return "Policy: " + String.valueOf(policy.getTarget()); } private static String buildReposHeader() { StringBuilder sb = new StringBuilder(); sb.append("Repos: "); List roots = RepoScannerLite.roots(); boolean first = true; for (Path r : roots) { if (r == null || !Files.isDirectory(r)) continue; if (!first) sb.append(", "); sb.append(r.toString()); first = false; } if (first) sb.append("(none)"); return sb.toString(); } private static void scanAsync(MultiWindowTextGUI gui, List all, Runnable refresh, boolean[] scanning) { if (scanning[0]) return; scanning[0] = true; all.clear(); refresh.run(); new Thread(() -> { try { for (Path root : RepoScannerLite.roots()) { if (root == null || !Files.isDirectory(root)) continue; try (Stream s = Files.list(root)) { for (Path p : (Iterable) s::iterator) { if (!Files.isDirectory(p)) continue; try { ExtensionInspectorLite.Report r = ExtensionInspectorLite.inspectDirectory(p); synchronized (all) { all.add(new Item(r.id, r.version, r.privileged, p, r.requiredPermNames)); } gui.getGUIThread().invokeLater(refresh); } catch (Exception ignored) {} } } catch (Exception ignored) {} } } finally { scanning[0] = false; gui.getGUIThread().invokeLater(refresh); } }, "btracex-scan").start(); } private static final class Item { final String id; final String version; final boolean privileged; final Path dir; final List requiredPerms; Item(String id, String version, boolean privileged, Path dir) { this(id, version, privileged, dir, Collections.emptyList()); } Item(String id, String version, boolean privileged, Path dir, List requiredPerms) { this.id = id; this.version = version; this.privileged = privileged; this.dir = dir; this.requiredPerms = requiredPerms; } } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/tui/ExtensionInspectorLite.java ================================================ package org.openjdk.btrace.extcli.tui; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.*; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.stream.Stream; import org.openjdk.btrace.core.extensions.Extension; import org.openjdk.btrace.core.extensions.ExtensionMeta; import org.openjdk.btrace.core.extensions.Permission; final class ExtensionInspectorLite { static class Report { final String id; final String version; final boolean privileged; final Set services; final java.util.List requiredPermNames; Report(String id, String version, boolean privileged, Set services, java.util.List requiredPermNames) { this.id = id; this.version = version; this.privileged = privileged; this.services = services; this.requiredPermNames = requiredPermNames != null ? requiredPermNames : java.util.Collections.emptyList(); } } static Report inspectDirectory(Path dir) throws IOException { Path api = findFirstDeep(dir, "-api.jar"); Path impl = findFirstDeep(dir, "-impl.jar"); if (api == null || impl == null) throw new IOException("Missing api/impl jars under " + dir); String id = readIdFromJar(api); if (id == null || id.isEmpty()) { String n = dir.getFileName() != null ? dir.getFileName().toString() : dir.toString(); id = stripVersionFromName(n); } String version = readVersionFromJar(api); if (version == null || version.isEmpty()) { String n = api.getFileName() != null ? api.getFileName().toString() : api.toString(); version = n.replaceFirst("^[^-]+-", "").replaceFirst("-api\\.jar$", ""); } List perms = new ArrayList<>(); perms.addAll(readPermissionsFromManifestOrProps(api)); perms.addAll(readPermissionsFromManifestOrProps(impl)); LinkedHashSet ded = new LinkedHashSet<>(); for (String n : perms) { String t = n.trim(); if (!t.isEmpty()) ded.add(t); } boolean privileged = computePrivileged(api, impl); Set services = readServices(impl); return new Report(id, version, privileged, services, new ArrayList<>(ded)); } private static String stripVersionFromName(String name) { if (name == null || name.isEmpty()) return name; int idx = name.lastIndexOf('-'); if (idx > 0 && idx + 1 < name.length() && Character.isDigit(name.charAt(idx + 1))) { return name.substring(0, idx); } return name; } private static Path findFirstDeep(Path root, String suffix) throws IOException { try (Stream s = Files.walk(root)) { Optional p = s.filter(pth -> pth.getFileName() != null && pth.getFileName().toString().endsWith(suffix)).findFirst(); return p.orElse(null); } } private static String readVersionFromJar(Path apiJar) { try (JarFile jf = new JarFile(apiJar.toFile())) { Manifest mf = jf.getManifest(); if (mf != null) { String v = mf.getMainAttributes().getValue("Implementation-Version"); if (v != null) return v; v = mf.getMainAttributes().getValue("BTrace-Extension-Version"); if (v != null) return v; } } catch (IOException ignored) {} return ""; } private static String readIdFromJar(Path jarPath) { try (JarFile jf = new JarFile(jarPath.toFile())) { Manifest mf = jf.getManifest(); if (mf != null) { String id = mf.getMainAttributes().getValue("BTrace-Extension-Id"); if (id != null && !id.isEmpty()) return id; } JarEntry props = jf.getJarEntry("META-INF/btrace-extension.properties"); if (props != null) { Properties p = new Properties(); try (InputStream is = jf.getInputStream(props)) { p.load(is); } String id = p.getProperty("extension.id", ""); if (!id.isEmpty()) return id; } } catch (IOException ignored) {} return ""; } private static Set readServices(Path implJar) { Set services = new HashSet<>(); try (JarFile jf = new JarFile(implJar.toFile())) { Enumeration en = jf.entries(); while (en.hasMoreElements()) { JarEntry e = en.nextElement(); if (e.getName().startsWith("META-INF/services/") && !e.isDirectory()) { services.add(e.getName().substring("META-INF/services/".length())); } } } catch (IOException ignored) {} return services; } private static boolean computePrivileged(Path apiJar, Path implJar) { List names = new ArrayList<>(); names.addAll(readPermissionsFromManifestOrProps(apiJar)); names.addAll(readPermissionsFromManifestOrProps(implJar)); for (String n : names) { String u = n.trim().toUpperCase(); if (u.equals("FILE_WRITE") || u.equals("NETWORK") || u.equals("THREADS") || u.equals("NATIVE") || u.equals("EXEC") || u.equals("REFLECTION") || u.equals("CLASSLOADER") || u.equals("UNLIMITED_MEMORY")) { return true; } } return false; } private static List readPermissionsFromManifestOrProps(Path jarPath) { List perms = new ArrayList<>(); try (JarFile jf = new JarFile(jarPath.toFile())) { Manifest mf = jf.getManifest(); if (mf != null) { String v = mf.getMainAttributes().getValue("BTrace-Extension-Permissions"); if (v != null && !v.trim().isEmpty()) { for (String part : v.split(",")) { String s = part.trim(); if (!s.isEmpty()) perms.add(s); } return perms; } } JarEntry e = jf.getJarEntry("META-INF/btrace-extension.properties"); if (e != null) { Properties p = new Properties(); try (InputStream is = jf.getInputStream(e)) { p.load(is); } String v = p.getProperty("requires.permissions", ""); if (!v.isEmpty()) { for (String part : v.split(",")) { String s = part.trim(); if (!s.isEmpty()) perms.add(s); } } } } catch (IOException ignored) {} return perms; } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/tui/PolicyFileLite.java ================================================ package org.openjdk.btrace.extcli.tui; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.nio.file.StandardCopyOption; final class PolicyFileLite { private final Properties props = new Properties(); private final Path target; static PolicyFileLite homeDefault() throws IOException { Path t = Path.of(System.getProperty("user.home"), ".btrace", "permissions.properties"); return new PolicyFileLite(t); } PolicyFileLite(Path target) throws IOException { this.target = target; loadIfExists(); } Path getTarget() { return target; } boolean isAllowed(String id) { return list("allowExtensions").contains(id); } boolean isDenied(String id) { return list("denyExtensions").contains(id); } void allow(String id) { alterLists(id, true); } void deny(String id) { alterLists(id, false); } void clear(String id) { List al = list("allowExtensions"); if (al.remove(id)) props.setProperty("allowExtensions", String.join(",", al)); List dl = list("denyExtensions"); if (dl.remove(id)) props.setProperty("denyExtensions", String.join(",", dl)); } private void alterLists(String id, boolean allow) { List al = list("allowExtensions"); List dl = list("denyExtensions"); if (allow) { if (!al.contains(id)) al.add(id); dl.remove(id); } else { if (!dl.contains(id)) dl.add(id); al.remove(id); } props.setProperty("allowExtensions", String.join(",", al)); props.setProperty("denyExtensions", String.join(",", dl)); } private List list(String key) { List res = new ArrayList<>(); String csv = props.getProperty(key, ""); if (csv == null || csv.trim().isEmpty()) return res; for (String s : csv.split(",")) { String t = s.trim(); if (!t.isEmpty()) res.add(t); } return res; } void save() throws IOException { Files.createDirectories(target.getParent()); Path tmp = target.resolveSibling(target.getFileName() + ".tmp"); try (OutputStream os = Files.newOutputStream(tmp)) { props.store(os, "BTrace permissions policy"); } Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } String getAllowPrivileged() { return props.getProperty("allowPrivileged", "false"); } void toggleAllowPrivileged() { String cur = props.getProperty("allowPrivileged", "false"); boolean b = Boolean.parseBoolean(cur); props.setProperty("allowPrivileged", Boolean.toString(!b)); } private void loadIfExists() throws IOException { if (Files.exists(target)) try (InputStream is = Files.newInputStream(target)) { props.load(is); } } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/tui/RepoScannerLite.java ================================================ package org.openjdk.btrace.extcli.tui; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; final class RepoScannerLite { static List roots() { List roots = new ArrayList<>(); String home = System.getenv("BTRACE_HOME"); if (home != null && !home.isEmpty()) roots.add(Path.of(home, "extensions")); roots.add(Path.of(System.getProperty("user.home"), ".btrace", "extensions")); String extra = System.getenv("BTRACE_EXT_PATH"); if (extra != null && !extra.isEmpty()) { for (String p : extra.split(File.pathSeparator)) roots.add(Path.of(p)); } return roots; } } ================================================ FILE: btrace-ext-cli/src/main/java/org/openjdk/btrace/extcli/tui/TuiState.java ================================================ package org.openjdk.btrace.extcli.tui; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Properties; final class TuiState { String filter = ""; String sortKey = "ID"; boolean sortAsc = true; double splitRatio = 0.65d; static TuiState load() { TuiState s = new TuiState(); try { Path dir = Path.of(System.getProperty("user.home"), ".btrace"); Path f = dir.resolve("tui-state.properties"); if (Files.exists(f)) { Properties p = new Properties(); try (InputStream is = Files.newInputStream(f)) { p.load(is); } s.filter = p.getProperty("filter", s.filter); s.sortKey = p.getProperty("sortKey", s.sortKey); s.sortAsc = Boolean.parseBoolean(p.getProperty("sortAsc", Boolean.toString(s.sortAsc))); try { s.splitRatio = Double.parseDouble(p.getProperty("splitRatio", Double.toString(s.splitRatio))); } catch (NumberFormatException ignored) {} } } catch (IOException ignored) { } return s; } static void save(TuiState s) { try { Path dir = Path.of(System.getProperty("user.home"), ".btrace"); Files.createDirectories(dir); Path f = dir.resolve("tui-state.properties"); Properties p = new Properties(); p.setProperty("filter", s.filter != null ? s.filter : ""); p.setProperty("sortKey", s.sortKey != null ? s.sortKey : "ID"); p.setProperty("sortAsc", Boolean.toString(s.sortAsc)); p.setProperty("splitRatio", Double.toString(s.splitRatio)); try (OutputStream os = Files.newOutputStream(f)) { p.store(os, "BTrace TUI state"); } } catch (IOException ignored) { } } } ================================================ FILE: btrace-ext-cli/src/test/java/org/openjdk/btrace/extcli/ExtensionInspectorTest.java ================================================ /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extcli; import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class ExtensionInspectorTest { @TempDir Path tempDir; @Test void inspectValidDirectory() throws IOException { Path extDir = tempDir.resolve("test-extension"); TestExtensionBuilder.createExtensionDirectory("test-ext", "1.0.0", extDir, false); ExtensionReport report = ExtensionInspector.inspect(extDir); assertTrue(report.ok, "Extension should be valid"); assertEquals("test-ext", report.id); assertEquals("1.0.0", report.version); assertFalse(report.privileged, "Extension should not be privileged"); } @Test void inspectValidZip() throws IOException { Path zipFile = tempDir.resolve("test-extension.zip"); TestExtensionBuilder.createExtensionZip("test-ext", "2.0.0", zipFile, false); ExtensionReport report = ExtensionInspector.inspect(zipFile); assertTrue(report.ok, "Extension ZIP should be valid"); assertEquals("test-ext", report.id); assertEquals("2.0.0", report.version); } @Test void detectMissingApiJar() throws IOException { Path extDir = tempDir.resolve("incomplete-extension"); Files.createDirectories(extDir); // Only create impl jar, no api jar Path implJar = extDir.resolve("test-1.0.0-impl.jar"); TestExtensionBuilder.createImplJar("test", implJar); ExtensionReport report = ExtensionInspector.inspect(extDir); assertFalse(report.ok, "Extension should be invalid without API JAR"); assertTrue(report.message.contains("Missing api/impl jars")); } @Test void detectMissingImplJar() throws IOException { Path extDir = tempDir.resolve("incomplete-extension"); Files.createDirectories(extDir); // Only create api jar, no impl jar Path apiJar = extDir.resolve("test-1.0.0-api.jar"); TestExtensionBuilder.createApiJar("test", "1.0.0", apiJar, false); ExtensionReport report = ExtensionInspector.inspect(extDir); assertFalse(report.ok, "Extension should be invalid without implementation JAR"); assertTrue(report.message.contains("Missing api/impl jars")); } @Test void inspectExtensionWithPermissions() throws IOException { Path extDir = tempDir.resolve("permissions-extension"); TestExtensionBuilder.createExtensionDirectory("perm-ext", "1.0.0", extDir, true); ExtensionReport report = ExtensionInspector.inspect(extDir); assertTrue(report.ok, "Extension with permissions should be valid"); // Note: Privileged detection requires actual ExtensionMeta from compiled classes, // which is beyond the scope of simple unit testing with programmatic JAR creation } @Test void extractManifestId() throws IOException { Path extDir = tempDir.resolve("manifest-id-test"); TestExtensionBuilder.createExtensionDirectory("manifest-ext", "1.5.0", extDir, false); ExtensionReport report = ExtensionInspector.inspect(extDir); assertEquals("manifest-ext", report.id, "Should extract ID from manifest"); } } ================================================ FILE: btrace-ext-cli/src/test/java/org/openjdk/btrace/extcli/ExtensionListerTest.java ================================================ /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extcli; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class ExtensionListerTest { @TempDir Path tempDir; private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; private String originalBtraceHome; @BeforeEach void setUpStreams() { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); originalBtraceHome = System.getenv("BTRACE_HOME"); } @AfterEach void restoreStreams() { System.setOut(originalOut); System.setErr(originalErr); } @Test void listFromBtraceHome() throws IOException { // Create extensions directory with one extension Path extensionsDir = tempDir.resolve("extensions"); Files.createDirectories(extensionsDir); Path ext1 = extensionsDir.resolve("ext1"); TestExtensionBuilder.createExtensionDirectory("ext1", "1.0.0", ext1, false); // Set BTRACE_HOME temporarily using reflection (since we can't actually change env vars) // Instead, we'll just verify the lister doesn't crash with empty dirs ExtensionLister.list(false); // Should not crash, output may be empty since BTRACE_HOME is not set String output = outContent.toString(); assertNotNull(output); } @Test void listWithJsonFormat() throws IOException { ExtensionLister.list(true); String output = outContent.toString(); // Should output valid JSON (starts with [ and ends with ]) assertTrue( output.trim().startsWith("[") && output.trim().endsWith("]"), "JSON output should be an array"); } @Test void listHandlesEmptyDirectories() throws IOException { // Create empty extensions directory Path extensionsDir = tempDir.resolve("extensions"); Files.createDirectories(extensionsDir); // Should handle gracefully without errors assertDoesNotThrow(() -> ExtensionLister.list(false)); } @Test void listOutputsExtensionInfo() throws IOException { // Since we can't easily set environment variables, we verify the method // completes without throwing exceptions assertDoesNotThrow(() -> ExtensionLister.list(false)); assertDoesNotThrow(() -> ExtensionLister.list(true)); } } ================================================ FILE: btrace-ext-cli/src/test/java/org/openjdk/btrace/extcli/InstallerTest.java ================================================ /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extcli; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.file.Path; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class InstallerTest { @TempDir Path tempDir; private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; @BeforeEach void setUpStreams() { System.setOut(new PrintStream(outContent)); } @AfterEach void restoreStreams() { System.setOut(originalOut); } @Test void dryRunFromLocalZip() throws Exception { Path zipFile = tempDir.resolve("test-ext.zip"); TestExtensionBuilder.createExtensionZip("test-ext", "1.0.0", zipFile, false); Installer.install(zipFile.toString(), Collections.emptyList(), null, true); String output = outContent.toString(); assertTrue(output.contains("[DRY-RUN]"), "Should indicate dry-run mode"); assertTrue(output.contains("Would install"), "Should show install action"); } @Test void dryRunFromUrl() throws Exception { Installer.install( "https://example.com/test-ext.zip", Collections.emptyList(), null, true); String output = outContent.toString(); assertTrue(output.contains("[DRY-RUN]"), "Should indicate dry-run mode"); assertTrue(output.contains("Would download"), "Should show download action"); } @Test void dryRunFromMavenGav() throws Exception { List repos = List.of("https://repo1.maven.org/maven2"); Installer.install("org.example:test-ext:1.0.0", repos, null, true); String output = outContent.toString(); assertTrue(output.contains("[DRY-RUN]"), "Should indicate dry-run mode"); assertTrue(output.contains("Candidate URLs"), "Should show candidate Maven URLs"); } @Test void dryRunWithCustomId() throws Exception { Path zipFile = tempDir.resolve("test.zip"); TestExtensionBuilder.createExtensionZip("test-ext", "1.0.0", zipFile, false); Installer.install(zipFile.toString(), Collections.emptyList(), "custom-id", true); String output = outContent.toString(); assertTrue(output.contains("[DRY-RUN]"), "Should indicate dry-run mode"); } @Test void invalidGavCoordinateThrowsException() { assertThrows( IllegalArgumentException.class, () -> Installer.install("invalid:coordinate", Collections.emptyList(), null, true), "Should reject invalid GAV coordinate"); } @Test void unrecognizedInputThrowsException() { assertThrows( IllegalArgumentException.class, () -> Installer.install("not-a-valid-input", Collections.emptyList(), null, true), "Should reject unrecognized input"); } @Test void multipleReposInDryRun() throws Exception { List repos = List.of("https://repo1.example.com", "https://repo2.example.com"); Installer.install("com.example:test:1.0", repos, null, true); String output = outContent.toString(); assertTrue( output.contains("repo1.example.com") && output.contains("repo2.example.com"), "Should show all candidate repositories"); } @Test void derivesIdFromZipFilename() throws Exception { Path zipFile = tempDir.resolve("my-extension-1.2.3.zip"); TestExtensionBuilder.createExtensionZip("my-ext", "1.2.3", zipFile, false); Installer.install(zipFile.toString(), Collections.emptyList(), null, true); String output = outContent.toString(); assertTrue(output.contains("[DRY-RUN]"), "Should complete dry-run"); } } ================================================ FILE: btrace-ext-cli/src/test/java/org/openjdk/btrace/extcli/MainTest.java ================================================ /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extcli; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Path; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class MainTest { @TempDir Path tempDir; private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; @BeforeEach void setUpStreams() { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); } @AfterEach void restoreStreams() { System.setOut(originalOut); System.setErr(originalErr); } @Test void showsHelpWithNoArgs() throws Exception { Main.main(new String[] {}); String output = outContent.toString(); assertTrue(output.contains("Usage") || output.contains("usage"), "Should show usage"); } @Test void showsHelpWithHelpFlag() throws Exception { Main.main(new String[] {"--help"}); String output = outContent.toString(); assertTrue(output.contains("Usage") || output.contains("usage"), "Should show usage"); } @Test void inspectCommandWithValidExtension() throws Exception { Path extDir = tempDir.resolve("test-ext"); TestExtensionBuilder.createExtensionDirectory("test-ext", "1.0.0", extDir, false); Main.main(new String[] {"inspect", extDir.toString()}); String output = outContent.toString(); assertTrue(output.contains("test-ext"), "Should display extension ID"); } @Test void inspectCommandWithJsonFlag() throws Exception { Path extDir = tempDir.resolve("test-ext"); TestExtensionBuilder.createExtensionDirectory("test-ext", "1.0.0", extDir, false); Main.main(new String[] {"inspect", extDir.toString(), "--json"}); String output = outContent.toString(); assertTrue(output.contains("{") && output.contains("}"), "Should output JSON"); assertTrue(output.contains("\"id\""), "JSON should contain id field"); } @Test void listCommandExecutes() throws Exception { Main.main(new String[] {"list"}); // Should execute without throwing exception String output = outContent.toString(); assertNotNull(output); } @Test void unknownCommandShowsError() throws Exception { Main.main(new String[] {"invalid-command"}); String error = errContent.toString(); assertTrue(error.contains("Unknown command"), "Should show unknown command error"); } } ================================================ FILE: btrace-ext-cli/src/test/java/org/openjdk/btrace/extcli/PolicyFileTest.java ================================================ package org.openjdk.btrace.extcli; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.IOException; import java.nio.file.Path; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; class PolicyFileTest { @TempDir Path tempDir; @Test void saveAndReloadPolicy() throws IOException { Path policy = tempDir.resolve("permissions.properties"); PolicyFile pf = PolicyFile.fromArgs(new String[] {"--policy-file", policy.toString()}); pf.updateFromArgs(new String[] {"--allowExtensions", "a,b", "--denyExtensions", "c", "--allowPrivileged", "true"}); pf.save(); PolicyFile reloaded = PolicyFile.fromArgs(new String[] {"--policy-file", policy.toString()}); assertEquals(List.of("a", "b"), reloaded.getAllowList()); assertEquals(List.of("c"), reloaded.getDenyList()); assertTrue(reloaded.describe(true).contains("\"allowPrivileged\":\"true\"")); } @Test void allowDenyAndClearUpdateLists() throws IOException { Path policy = tempDir.resolve("policy.properties"); PolicyFile pf = PolicyFile.fromArgs(new String[] {"--policy-file", policy.toString()}); pf.allow("ext-a"); assertEquals(List.of("ext-a"), pf.getAllowList()); assertEquals(List.of(), pf.getDenyList()); pf.deny("ext-a"); assertEquals(List.of(), pf.getAllowList()); assertEquals(List.of("ext-a"), pf.getDenyList()); pf.clear("ext-a"); assertEquals(List.of(), pf.getAllowList()); assertEquals(List.of(), pf.getDenyList()); } @Test void rejectsMultipleTargets() { assertThrows( IllegalArgumentException.class, () -> PolicyFile.fromArgs(new String[] {"--policy-file", "a", "--home"})); } @Test void classpathTargetResolvesToMetaInf() throws IOException { PolicyFile pf = PolicyFile.fromArgs(new String[] {"--classpath", tempDir.toString()}); Path expected = tempDir.resolve("META-INF/btrace/permissions.properties"); assertEquals(expected, pf.getTarget()); } } ================================================ FILE: btrace-ext-cli/src/test/java/org/openjdk/btrace/extcli/TestExtensionBuilder.java ================================================ /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extcli; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * Helper class to build test extension JARs programmatically. */ class TestExtensionBuilder { /** * Creates a valid API JAR with extension metadata. * * @param id Extension ID * @param version Extension version * @param output Output file path * @param privileged Whether extension requires privileged permissions * @throws IOException if JAR creation fails */ static void createApiJar(String id, String version, Path output, boolean privileged) throws IOException { Manifest manifest = new Manifest(); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); manifest.getMainAttributes().putValue("BTrace-Extension-Id", id); manifest.getMainAttributes().putValue("BTrace-Extension-Version", version); try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(output.toFile()), manifest)) { // Add META-INF/btrace/exports.index jos.putNextEntry(new JarEntry("META-INF/btrace/")); jos.closeEntry(); jos.putNextEntry(new JarEntry("META-INF/btrace/exports.index")); jos.write("org.example.TestService\n".getBytes(StandardCharsets.UTF_8)); jos.closeEntry(); // Add permissions.properties if privileged if (privileged) { jos.putNextEntry(new JarEntry("META-INF/btrace/permissions.properties")); jos.write("permissions=IO,NETWORK\n".getBytes(StandardCharsets.UTF_8)); jos.closeEntry(); } } } /** * Creates a valid implementation JAR. * * @param id Extension ID * @param output Output file path * @throws IOException if JAR creation fails */ static void createImplJar(String id, Path output) throws IOException { Manifest manifest = new Manifest(); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(output.toFile()), manifest)) { // Add META-INF/services entry jos.putNextEntry(new JarEntry("META-INF/services/")); jos.closeEntry(); jos.putNextEntry(new JarEntry("META-INF/services/org.example.TestService")); jos.write("org.example.TestServiceImpl\n".getBytes(StandardCharsets.UTF_8)); jos.closeEntry(); // Add a dummy class file jos.putNextEntry(new JarEntry("org/example/TestServiceImpl.class")); // Minimal valid class file (empty class) byte[] classBytes = createMinimalClassFile(); jos.write(classBytes); jos.closeEntry(); } } /** * Creates a valid extension ZIP containing API and implementation JARs. * * @param id Extension ID * @param version Extension version * @param output Output ZIP file path * @param privileged Whether extension requires privileged permissions * @throws IOException if ZIP creation fails */ static void createExtensionZip(String id, String version, Path output, boolean privileged) throws IOException { Path tempDir = Files.createTempDirectory("btrace-ext-test"); try { Path apiJar = tempDir.resolve(id + "-" + version + "-api.jar"); Path implJar = tempDir.resolve(id + "-" + version + "-impl.jar"); createApiJar(id, version, apiJar, privileged); createImplJar(id, implJar); try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(output.toFile()))) { addFileToZip(zos, apiJar, apiJar.getFileName().toString()); addFileToZip(zos, implJar, implJar.getFileName().toString()); } } finally { // Recursively clean up temp directory and all contents Files.walk(tempDir) .sorted(Comparator.reverseOrder()) .forEach( p -> { try { Files.deleteIfExists(p); } catch (IOException ignored) { } }); } } /** * Creates an extension directory structure with API and implementation JARs. * * @param id Extension ID * @param version Extension version * @param outputDir Output directory path * @param privileged Whether extension requires privileged permissions * @throws IOException if creation fails */ static void createExtensionDirectory(String id, String version, Path outputDir, boolean privileged) throws IOException { Files.createDirectories(outputDir); Path apiJar = outputDir.resolve(id + "-" + version + "-api.jar"); Path implJar = outputDir.resolve(id + "-" + version + "-impl.jar"); createApiJar(id, version, apiJar, privileged); createImplJar(id, implJar); } private static void addFileToZip(ZipOutputStream zos, Path file, String name) throws IOException { zos.putNextEntry(new ZipEntry(name)); Files.copy(file, zos); zos.closeEntry(); } /** * Creates a minimal structurally valid Java class file for testing. * *

Constant pool layout: * *

   *   #1 CONSTANT_Class -> #3
   *   #2 CONSTANT_Class -> #4
   *   #3 CONSTANT_Utf8  "org/example/TestServiceImpl"
   *   #4 CONSTANT_Utf8  "java/lang/Object"
   * 
*/ private static byte[] createMinimalClassFile() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Magic number writeU4(baos, 0xCAFEBABEL); // Minor version: 0, Major version: 52 (Java 8) writeU2(baos, 0); writeU2(baos, 52); // Constant pool count: 5 (entries 1..4) writeU2(baos, 5); // #1 CONSTANT_Class -> #3 baos.write(7); writeU2(baos, 3); // #2 CONSTANT_Class -> #4 baos.write(7); writeU2(baos, 4); // #3 CONSTANT_Utf8 "org/example/TestServiceImpl" byte[] thisName = "org/example/TestServiceImpl".getBytes(StandardCharsets.UTF_8); baos.write(1); writeU2(baos, thisName.length); baos.write(thisName, 0, thisName.length); // #4 CONSTANT_Utf8 "java/lang/Object" byte[] superName = "java/lang/Object".getBytes(StandardCharsets.UTF_8); baos.write(1); writeU2(baos, superName.length); baos.write(superName, 0, superName.length); // Access flags: public writeU2(baos, 0x0021); // This class: #1 writeU2(baos, 1); // Super class: #2 writeU2(baos, 2); // Interfaces count: 0 writeU2(baos, 0); // Fields count: 0 writeU2(baos, 0); // Methods count: 0 writeU2(baos, 0); // Attributes count: 0 writeU2(baos, 0); return baos.toByteArray(); } private static void writeU2(ByteArrayOutputStream baos, int value) { baos.write((value >> 8) & 0xFF); baos.write(value & 0xFF); } private static void writeU4(ByteArrayOutputStream baos, long value) { baos.write((int) ((value >> 24) & 0xFF)); baos.write((int) ((value >> 16) & 0xFF)); baos.write((int) ((value >> 8) & 0xFF)); baos.write((int) (value & 0xFF)); } } ================================================ FILE: btrace-extension/build.gradle ================================================ buildscript { scriptHandler -> apply from: rootProject.file('buildSrc/shared.gradle'), to: scriptHandler } plugins { alias(libs.plugins.versioning) id 'jacoco' } dependencies { compileOnly project(':btrace-core') implementation libs.slf4j implementation libs.slf4j.simple testImplementation project(':btrace-core') } jacoco { toolVersion = "0.8.14" } jacocoTestReport { dependsOn test reports { xml.required = true html.required = true csv.required = false } // Focus on v2 protocol package afterEvaluate { classDirectories.setFrom(files(classDirectories.files.collect { fileTree(dir: it, include: [ '**/org/openjdk/btrace/core/comm/**/*.class' ]) })) } } test { finalizedBy jacocoTestReport } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/ExtensionBridge.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension; /** * Bridge interface for accessing BTrace extensions from invokedynamic bootstrap methods. * Implemented by agent-side code to provide access to extension classes. */ public interface ExtensionBridge { /** * Load and return the specified extension service class. * * @param serviceClassName fully qualified service class name * @return service class, or null if not found * @throws Exception if class loading fails */ Class getExtensionClass(String serviceClassName) throws Exception; } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/ExtensionDescriptorDTO.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension; import org.openjdk.btrace.core.extensions.PermissionSet; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Objects; /** * Descriptor for a BTrace extension loaded from an extension JAR. * Contains metadata about the extension including identity, version, * compatibility requirements, and provided services. */ public final class ExtensionDescriptorDTO { private final String id; private final String version; private final String name; private final String description; private final Path jarPath; private final String btraceApiVersion; private final String javaVersion; private final List services; private final List requiredExtensions; private final ExtensionRepository repository; private final PermissionSet requiredPermissions; private volatile boolean loaded = false; private volatile ClassLoader classLoader = null; ExtensionDescriptorDTO( String id, String version, String name, String description, Path jarPath, String btraceApiVersion, String javaVersion, List services, List requiredExtensions, ExtensionRepository repository, PermissionSet requiredPermissions) { this.id = Objects.requireNonNull(id, "Extension id cannot be null"); this.version = Objects.requireNonNull(version, "Extension version cannot be null"); this.name = name != null ? name : id; this.description = description != null ? description : ""; this.jarPath = Objects.requireNonNull(jarPath, "Extension jar path cannot be null"); this.btraceApiVersion = btraceApiVersion != null ? btraceApiVersion : "2.0+"; this.javaVersion = javaVersion != null ? javaVersion : "8+"; this.services = services != null ? Collections.unmodifiableList(services) : Collections.emptyList(); this.requiredExtensions = requiredExtensions != null ? Collections.unmodifiableList(requiredExtensions) : Collections.emptyList(); this.repository = repository; this.requiredPermissions = requiredPermissions != null ? requiredPermissions : PermissionSet.empty(); } public String getId() { return id; } public String getVersion() { return version; } public String getName() { return name; } public String getDescription() { return description; } public Path getJarPath() { return jarPath; } public String getBtraceApiVersion() { return btraceApiVersion; } public String getJavaVersion() { return javaVersion; } public List getServices() { return services; } public List getRequiredExtensions() { return requiredExtensions; } public ExtensionRepository getRepository() { return repository; } /** * Returns permissions required by this extension (from manifest or properties), * or an empty set if none were declared. */ public PermissionSet getRequiredPermissions() { return requiredPermissions; } public boolean isLoaded() { return loaded; } public ClassLoader getClassLoader() { return classLoader; } public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; this.loaded = true; } /** * Check if this extension provides the given service class. * * @param serviceClassName fully qualified service class name * @return true if this extension provides the service */ public boolean providesService(String serviceClassName) { return services.contains(serviceClassName); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExtensionDescriptorDTO that = (ExtensionDescriptorDTO) o; return id.equals(that.id) && version.equals(that.version); } @Override public int hashCode() { return Objects.hash(id, version); } @Override public String toString() { return "ExtensionDescriptor{" + "id='" + id + '\'' + ", version='" + version + '\'' + ", name='" + name + '\'' + ", jarPath=" + jarPath + ", loaded=" + loaded + '}'; } /** Builder for creating ExtensionDescriptor instances. */ public static final class Builder { private String id; private String version; private String name; private String description; private Path jarPath; private String btraceApiVersion; private String javaVersion; private List services; private List requiredExtensions; private ExtensionRepository repository; private PermissionSet requiredPermissions; public Builder id(String id) { this.id = id; return this; } public Builder version(String version) { this.version = version; return this; } public Builder name(String name) { this.name = name; return this; } public Builder description(String description) { this.description = description; return this; } public Builder jarPath(Path jarPath) { this.jarPath = jarPath; return this; } public Builder btraceApiVersion(String btraceApiVersion) { this.btraceApiVersion = btraceApiVersion; return this; } public Builder javaVersion(String javaVersion) { this.javaVersion = javaVersion; return this; } public Builder services(List services) { this.services = services; return this; } public Builder requiredExtensions(List requiredExtensions) { this.requiredExtensions = requiredExtensions; return this; } public Builder repository(ExtensionRepository repository) { this.repository = repository; return this; } public Builder requiredPermissions(PermissionSet permissions) { this.requiredPermissions = permissions; return this; } public ExtensionDescriptorDTO build() { return new ExtensionDescriptorDTO( id, version, name, description, jarPath, btraceApiVersion, javaVersion, services, requiredExtensions, repository, requiredPermissions); } } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/ExtensionLoader.java ================================================ package org.openjdk.btrace.extension; import org.openjdk.btrace.extension.impl.ExtensionConfig; import org.openjdk.btrace.extension.impl.ExtensionLoaderImpl; import org.openjdk.btrace.extension.impl.FileSystemExtensionRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.lang.instrument.Instrumentation; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicReference; public abstract class ExtensionLoader { private static Logger log = LoggerFactory.getLogger(ExtensionLoader.class); private static final AtomicReference implRef = new AtomicReference<>(null); public static ExtensionLoader instance() { return implRef.get(); } public static ExtensionLoader initialize(String btraceHome, ClassLoader parentClassLoader, Instrumentation instrumentation) { // Create extension repositories in priority order List repositories = new ArrayList<>(); // 1. Built-in extensions (lowest priority) Path builtinExtPath = new File(btraceHome, "extensions").toPath(); repositories.add( new FileSystemExtensionRepository(builtinExtPath, ExtensionRepository.Priority.BUILTIN)); // 2. User extensions (~/.btrace/extensions/) String userHome = System.getProperty("user.home"); if (userHome != null) { Path userExtPath = new File(userHome, ".btrace/extensions").toPath(); repositories.add( new FileSystemExtensionRepository(userExtPath, ExtensionRepository.Priority.USER)); } // 3. Environment variable BTRACE_EXT_PATH String extPath = System.getenv("BTRACE_EXT_PATH"); if (extPath != null && !extPath.isEmpty()) { String[] paths = extPath.split(File.pathSeparator); for (String path : paths) { repositories.add( new FileSystemExtensionRepository( new File(path).toPath(), ExtensionRepository.Priority.ENVIRONMENT)); } } // Load extension configuration if (log.isDebugEnabled()) { log.debug("Loading extension config from: {}", btraceHome); } ExtensionConfig config = ExtensionConfig.load(btraceHome); ExtensionLoader instance = new ExtensionLoaderImpl(repositories, parentClassLoader, config, instrumentation); // Register service declaration resolver for bytecode-level validation. // Bytecode verifier (instr) uses this to check @Injected fields without loading classes. // Runtime reflection in Client#validateDeclaredServices complements this by checking // actual loadability and module/classloader access in the target JVM. ServiceDeclarationRegistry.setResolver( fqcn -> instance.findExtensionForService(fqcn) != null); // Discover all available extensions if (log.isDebugEnabled()) { log.debug("Discovering extensions..."); } instance.discoverExtensions(); if (log.isDebugEnabled()) { log.debug("Extension system initialized with {} available extension(s)", instance.getAvailableExtensions().size()); } log.info("Extension system initialized with {} available extension(s)", instance.getAvailableExtensions().size()); return instance; } /** * Find an extension that provides the given service class. * * @param serviceClassName fully qualified service class name * @return extension descriptor, or null if not found */ public abstract ExtensionDescriptorDTO findExtensionForService(String serviceClassName); /** * Discover all available extensions from configured repositories. * This should be called once during agent startup. * * @return list of discovered extensions */ public abstract List discoverExtensions(); /** * Get all available (discovered) extensions. * * @return collection of available extension descriptors */ public abstract Collection getAvailableExtensions(); /** * Ensure the extension API JAR is appended to the bootstrap classpath without * attempting to load the implementation JAR. This enables BTrace to generate * shims against the API when implementation use is blocked (e.g., permissions). * * @param descriptor the extension descriptor * @return true if the API JAR was found and appended; false otherwise */ public abstract boolean ensureApiOnBootstrap(ExtensionDescriptorDTO descriptor); /** * Load an extension and make its classes available. * This is idempotent - loading an already-loaded extension is a no-op. * * @param descriptor extension to load * @return true if loaded successfully, false otherwise */ public abstract boolean load(ExtensionDescriptorDTO descriptor); } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/ExtensionRegistry.java ================================================ package org.openjdk.btrace.extension; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Minimal failure registry for extensions discovered/loaded via manifest-based loader. * *

Manifest and extension properties are the single source of truth. Extension discovery, * permission checks, and instantiation are handled by the manifest-based bridge/loader. This class * only exposes a stable map of failure reasons for diagnostics and UI. */ public final class ExtensionRegistry { private ExtensionRegistry() {} private static final ConcurrentHashMap failedExtensions = new ConcurrentHashMap<>(); public static Map getFailedExtensions() { return Collections.unmodifiableMap(failedExtensions); } public static void registerFailedExtension(String idOrName, String reason) { failedExtensions.put(idOrName, reason); } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/ExtensionRepository.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension; import java.util.List; /** * Repository for discovering BTrace extensions. * Implementations scan specific locations (file system directories, * remote repositories, etc.) for extension JARs and parse their metadata. */ public interface ExtensionRepository { /** * Scan this repository for available extensions. * * @return list of discovered extension descriptors */ List scan(); /** * Get the location identifier for this repository (e.g., directory path, URL). * * @return repository location */ String getLocation(); /** * Get the priority of this repository. Higher priority repositories * override lower priority ones when resolving extension conflicts. * Built-in repositories have lower priority than user repositories. * * @return repository priority (higher = more important) */ int getPriority(); /** Priority constants for common repository types. */ public static final class Priority { /** Built-in extensions in BTRACE_HOME/libs/ext/ */ public static final int BUILTIN = 0; /** System-wide extensions in /etc/btrace/ext/ or %PROGRAMDATA%\btrace\ext\ */ public static final int SYSTEM = 100; /** User extensions in ~/.btrace/ext/ */ public static final int USER = 200; /** Environment variable BTRACE_EXT_PATH */ public static final int ENVIRONMENT = 300; /** Command-line --ext-path argument */ public static final int COMMAND_LINE = 400; /** Script-local ./.btrace/ext/ */ public static final int SCRIPT_LOCAL = 500; private Priority() {} } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/PermissionPolicy.java ================================================ package org.openjdk.btrace.extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Minimal process-wide policy: per-extension allow/deny plus a global allowPrivileged flag. * *

Used to decide whether an extension implementation may be linked. API classes remain * available for SHIMs when implementation is blocked.

*/ public final class PermissionPolicy { private static final Logger log = LoggerFactory.getLogger(PermissionPolicy.class); private static final PermissionPolicy INSTANCE = new PermissionPolicy(); public static PermissionPolicy get() { return INSTANCE; } // allow/deny lists (extension IDs) - use thread-safe sets private final Set allowExtIds = ConcurrentHashMap.newKeySet(); private final Set denyExtIds = ConcurrentHashMap.newKeySet(); // global switch to allow all privileged extensions private volatile boolean allowPrivileged = false; private PermissionPolicy() {} public void setAllowExtensionsCsv(String csv) { parseCsvInto(csv, allowExtIds); } public void setDenyExtensionsCsv(String csv) { parseCsvInto(csv, denyExtIds); } public void setAllowPrivileged(boolean allow) { this.allowPrivileged = allow; } private static void parseCsvInto(String csv, Set target) { if (csv == null || csv.trim().isEmpty()) return; for (String s : csv.split(",")) { String v = s.trim(); if (!v.isEmpty()) target.add(v); } } public void loadFromDefaults() { String source = null; try { Properties props = new Properties(); // 1) explicit system property path String path = System.getProperty("btrace.permissions"); if (path != null && !path.isEmpty()) { File f = new File(path); if (f.exists()) { try (InputStream is = new FileInputStream(f)) { props.load(is); source = f.getAbsolutePath(); } } } // 2) user home locations if (source == null) { String home = System.getProperty("user.home"); if (home != null) { File f1 = new File(home, ".btrace/permissions.properties"); File f2 = new File(home, ".config/btrace/permissions.properties"); File fx = f1.exists() ? f1 : (f2.exists() ? f2 : null); if (fx != null) { try (InputStream is = new FileInputStream(fx)) { props.load(is); source = fx.getAbsolutePath(); } } } } // 3) classpath resource if (source == null) { InputStream is = ClassLoader.getSystemResourceAsStream("META-INF/btrace/permissions.properties"); if (is != null) { try (InputStream ris = is) { props.load(ris); source = "classpath:META-INF/btrace/permissions.properties"; } } } if (source != null) { parseProperties(props); if (log.isInfoEnabled()) { log.info("Loaded BTrace permission policy from {}", source); } } else { if (log.isDebugEnabled()) { log.debug("No BTrace permission policy found; using defaults"); } } } catch (Exception e) { log.warn("Failed to load BTrace permission policy: {}", e.getMessage()); } } private void parseProperties(Properties props) { setAllowExtensionsCsv(props.getProperty("allowExtensions", "")); setDenyExtensionsCsv(props.getProperty("denyExtensions", "")); String ap = props.getProperty("allowPrivileged", "false"); setAllowPrivileged(Boolean.parseBoolean(ap)); } public boolean isExplicitlyDenied(String extensionId) { return extensionId != null && denyExtIds.contains(extensionId); } public boolean isExplicitlyAllowed(String extensionId) { return extensionId != null && allowExtIds.contains(extensionId); } public boolean isAllowPrivileged() { return allowPrivileged; } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/ServiceDeclarationRegistry.java ================================================ package org.openjdk.btrace.extension; /** * Pluggable registry to validate whether a given service type is declared * by any available BTrace extension. * * How it is used: * - The agent wires a resolver at startup based on its ExtensionLoader; see * agent initialization. This lets bytecode-time validation (e.g., in * BTraceProbeNode) query declared services without loading any user classes. * - This check is intentionally lightweight and name-based. It complements the * runtime reflection validation in the agent (Client#validateDeclaredServices), * which runs in the actual target JVM and covers classloader identity, JPMS * access, and linkage/loadability. * * When unset, validation defaults to permissive to avoid blocking in environments * where the agent has not configured the resolver (e.g., tooling or tests that * do not initialize extensions). */ public final class ServiceDeclarationRegistry { private ServiceDeclarationRegistry() {} public interface Resolver { boolean isDeclaredService(String fqcn); } private static volatile Resolver resolver; public static void setResolver(Resolver r) { resolver = r; } public static boolean isDeclaredService(String fqcn) { Resolver r = resolver; if (r == null) { return true; // permissive by default if not configured } try { return r.isDeclaredService(fqcn); } catch (Throwable t) { return true; // do not block if resolver fails } } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/ExtensionBridgeImpl.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.PermissionSet; import org.openjdk.btrace.extension.ExtensionBridge; import org.openjdk.btrace.extension.ExtensionDescriptorDTO; import org.openjdk.btrace.extension.ExtensionLoader; import org.openjdk.btrace.extension.PermissionPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of ExtensionBridge for agent-side extension access. * Provides class loading for extension services accessed via invokedynamic. */ public final class ExtensionBridgeImpl implements ExtensionBridge { private static final Logger log = LoggerFactory.getLogger(ExtensionBridgeImpl.class); private final ExtensionLoader loader; public ExtensionBridgeImpl(ExtensionLoader loader) { this.loader = loader; } /** Initialize the invokedynamic bridge with a live ExtensionLoader. */ public static void initialize(ExtensionLoader loader) { try { Class indyClz = Class.forName("org.openjdk.btrace.runtime.ExtensionIndy"); ExtensionBridge bridge = new ExtensionBridgeImpl(loader); indyClz.getField("bridge").set(null, bridge); log.debug("ExtensionIndy.bridge initialized"); } catch (ClassNotFoundException e) { log.debug("ExtensionIndy not available (expected for older Java versions)"); } catch (Throwable t) { log.warn("Unable to initialize ExtensionIndy.bridge", t); } } @Override public Class getExtensionClass(String serviceClassName) throws Exception { // 1) Locate the providing extension ExtensionDescriptorDTO ext = loader.findExtensionForService(serviceClassName); if (ext == null) { log.error("No extension found providing service: {}", serviceClassName); org.openjdk.btrace.extension.ExtensionRegistry.registerFailedExtension(serviceClassName, "No providing extension found"); return null; } if (log.isDebugEnabled()) { log.debug("ExtensionBridge: service {} provided by extension {} at {} (loaded={})", serviceClassName, ext.getId(), ext.getJarPath(), ext.isLoaded()); } // 2) Enforce policy (deny list / privileged requirements) PermissionPolicy policy = PermissionPolicy.get(); if (policy.isExplicitlyDenied(ext.getId())) { return fallbackInterface(ext, serviceClassName, "Blocked by policy (denyExtensions)"); } if (requiresPrivileged(ext) && !(policy.isAllowPrivileged() || policy.isExplicitlyAllowed(ext.getId()))) { log.warn("Blocking privileged extension {}. Allow via allowExtensions or allowPrivileged.", ext.getId()); return fallbackInterface(ext, serviceClassName, "Blocked privileged extension. Required=" + ext.getRequiredPermissions()); } // 3) Load extension if needed if (!ext.isLoaded()) { if (!loader.load(ext)) { log.error("Failed to load extension {} for service {}", ext.getId(), serviceClassName); org.openjdk.btrace.extension.ExtensionRegistry.registerFailedExtension(ext.getId(), "Failed to load extension"); return null; } } // 4) Resolve implementation class from extension classloader ClassLoader extCl = ext.getClassLoader(); if (extCl == null) { log.error("Extension {} has no classloader", ext.getId()); return null; } Class serviceInterface = extCl.loadClass(serviceClassName); Class impl = findImplementationClass(serviceInterface, extCl); if (impl != null) return impl; // 5) Try context classloader as a relaxed fallback (useful in tests) try { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); if (tccl != null && tccl != extCl) { Class altIface = tccl.loadClass(serviceInterface.getName()); Class altImpl = findImplementationClass(altIface, tccl); if (altImpl != null) return altImpl; } } catch (Throwable ignore) { // ignore and fall back to interface } // 6) Fallback to service interface (runtime will shim as needed) if (log.isDebugEnabled()) { log.debug("ExtensionBridge: falling back to interface {} for service {}", serviceInterface.getName(), serviceClassName); } return serviceInterface; } private boolean requiresPrivileged(ExtensionDescriptorDTO ext) { PermissionSet required = ext.getRequiredPermissions(); if (required == null) return false; for (Permission p : required) { if (p.isPrivileged()) return true; } return false; } private Class fallbackInterface(ExtensionDescriptorDTO ext, String serviceClassName, String reason) { try { loader.ensureApiOnBootstrap(ext); Class intf = Class.forName(serviceClassName, false, null); org.openjdk.btrace.extension.ExtensionRegistry.registerFailedExtension(ext.getId(), reason); return intf; } catch (ClassNotFoundException cnfe) { // Even if API interface is not on bootstrap in this environment, keep the original reason // to accurately reflect why the implementation was not linked. org.openjdk.btrace.extension.ExtensionRegistry.registerFailedExtension(ext.getId(), reason); return null; } } private Class findImplementationClass(Class serviceInterface, ClassLoader cl) { String ifaceName = serviceInterface.getName(); // Prefer ServiceLoader to allow multiple providers and custom impl names try { java.util.ServiceLoader sl = java.util.ServiceLoader.load(serviceInterface, cl); for (Object prov : sl) { Class impl = prov.getClass(); if (serviceInterface.isAssignableFrom(impl)) { if (log.isDebugEnabled()) { log.debug("ExtensionBridge: using ServiceLoader provider {} for service {}", impl.getName(), ifaceName); } return impl; } } } catch (Throwable t) { // ignore and continue } // Conventional Impl naming: FooService -> FooServiceImpl String implCandidate = ifaceName + "Impl"; try { Class impl = cl.loadClass(implCandidate); if (serviceInterface.isAssignableFrom(impl)) { if (log.isDebugEnabled()) { log.debug("ExtensionBridge: using Impl candidate {} for service {}", implCandidate, ifaceName); } return impl; } } catch (ClassNotFoundException ignore) { // no-op } return null; } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/ExtensionClassLoader.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import java.net.URL; import java.net.URLClassLoader; /** * ClassLoader for BTrace extensions. * Each extension is loaded in its own classloader for isolation. * The parent classloader should be the BTrace boot classloader that * contains core BTrace API classes. * *

Delegation model: *

 * Bootstrap ClassLoader (JRE classes)
 *     ↓
 * BTrace Boot ClassLoader (btrace-boot.jar - core API)
 *     ↓
 * Extension ClassLoader (extension JAR - extension code + shaded deps)
 * 
* *

Extensions can see: * - JRE classes (bootstrap) * - BTrace core API classes (parent) * - Their own classes and shaded dependencies (this classloader) * *

Extensions cannot see: * - Other extension's classes * - Agent implementation classes */ public final class ExtensionClassLoader extends URLClassLoader { private final String extensionId; private final String extensionVersion; /** * Create an extension classloader. * * @param extensionId extension identifier * @param extensionVersion extension version * @param urls URLs to load extension classes from (typically single JAR) * @param parent parent classloader (typically BTrace boot classloader) */ public ExtensionClassLoader( String extensionId, String extensionVersion, URL[] urls, ClassLoader parent) { super(urls, parent); this.extensionId = extensionId; this.extensionVersion = extensionVersion; } /** * Get the extension identifier. * * @return extension ID */ public String getExtensionId() { return extensionId; } /** * Get the extension version. * * @return extension version */ public String getExtensionVersion() { return extensionVersion; } @Override public String toString() { return "ExtensionClassLoader{" + "extension='" + extensionId + "' version='" + extensionVersion + "'}"; } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/ExtensionConfig.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.HashSet; import java.util.Properties; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Configuration for BTrace extension loading and behavior. * Supports whitelisting, blacklisting, and extension-specific settings. */ public final class ExtensionConfig { private static final Logger log = LoggerFactory.getLogger(ExtensionConfig.class); private static final String PROP_ENABLED = "extensions.enabled"; private static final String PROP_DISABLED = "extensions.disabled"; private static final String PROP_AUTOLOAD = "extensions.autoload"; private final Set enabledExtensions; private final Set disabledExtensions; private final boolean autoload; private final Properties allProperties; private ExtensionConfig( Set enabledExtensions, Set disabledExtensions, boolean autoload, Properties allProperties) { this.enabledExtensions = enabledExtensions; this.disabledExtensions = disabledExtensions; this.autoload = autoload; this.allProperties = allProperties; } /** * Load configuration from default locations. * Priority: command-line {@literal >} user config {@literal >} system config {@literal >} defaults * * @param btraceHome BTRACE_HOME directory (for system config) * @return loaded configuration */ public static ExtensionConfig load(String btraceHome) { // 1. Check command-line override String configPath = System.getProperty("btrace.extensions.config"); if (configPath != null) { File configFile = new File(configPath); if (configFile.exists()) { log.info("Loading extension configuration from: {}", configPath); return loadFrom(configFile.toPath()); } else { log.warn("Extension configuration file not found: {}", configPath); } } // 2. Check user config String userHome = System.getProperty("user.home"); if (userHome != null) { Path userConfig = Paths.get(userHome, ".btrace", "extensions.conf"); if (Files.exists(userConfig)) { log.info("Loading extension configuration from: {}", userConfig); return loadFrom(userConfig); } } // 3. Check system config if (btraceHome != null) { Path systemConfig = Paths.get(btraceHome, "conf", "extensions.conf"); if (Files.exists(systemConfig)) { log.info("Loading extension configuration from: {}", systemConfig); return loadFrom(systemConfig); } } // 4. Use defaults log.debug("No extension configuration found, using defaults"); return createDefault(); } /** * Load configuration from a specific file. * * @param configPath path to configuration file * @return loaded configuration */ public static ExtensionConfig loadFrom(Path configPath) { Properties props = new Properties(); try (InputStream in = new FileInputStream(configPath.toFile())) { props.load(in); } catch (IOException e) { log.error("Failed to load extension configuration from {}: {}", configPath, e.getMessage()); return createDefault(); } return parse(props); } /** * Create default configuration (all enabled, autoload). */ public static ExtensionConfig createDefault() { return new ExtensionConfig( Collections.emptySet(), Collections.emptySet(), true, new Properties()); } /** * Parse configuration from properties. */ private static ExtensionConfig parse(Properties props) { // Parse enabled list Set enabled = parseExtensionList(props.getProperty(PROP_ENABLED, "")); // Parse disabled list Set disabled = parseExtensionList(props.getProperty(PROP_DISABLED, "")); // Parse autoload boolean autoload = Boolean.parseBoolean(props.getProperty(PROP_AUTOLOAD, "true")); return new ExtensionConfig(enabled, disabled, autoload, props); } private static Set parseExtensionList(String value) { if (value == null || value.trim().isEmpty()) { return Collections.emptySet(); } Set extensions = new HashSet<>(); for (String ext : value.split("[,\\s]+")) { String trimmed = ext.trim(); if (!trimmed.isEmpty()) { extensions.add(trimmed); } } return extensions; } /** * Check if an extension is enabled by configuration. * * @param extensionId extension identifier * @return true if extension should be loaded */ public boolean isEnabled(String extensionId) { // Disabled list takes precedence if (disabledExtensions.contains(extensionId)) { return false; } // If enabled list is empty, all extensions are enabled // If enabled list is not empty, only listed extensions are enabled return enabledExtensions.isEmpty() || enabledExtensions.contains(extensionId); } /** * Get autoload setting. * * @return true if extensions should be loaded on demand, false to load all at startup */ public boolean isAutoLoad() { return autoload; } /** * Get extension-specific properties. * Returns properties with keys prefixed by {@literal }. * * @param extensionId extension identifier * @return properties for this extension (without prefix) */ public Properties getExtensionProperties(String extensionId) { Properties result = new Properties(); String prefix = extensionId + "."; for (String key : allProperties.stringPropertyNames()) { if (key.startsWith(prefix)) { String unprefixedKey = key.substring(prefix.length()); result.setProperty(unprefixedKey, allProperties.getProperty(key)); } } return result; } @Override public String toString() { StringBuilder sb = new StringBuilder("ExtensionConfig{"); sb.append("autoload=").append(autoload); if (!enabledExtensions.isEmpty()) { sb.append(", enabled=").append(enabledExtensions); } if (!disabledExtensions.isEmpty()) { sb.append(", disabled=").append(disabledExtensions); } sb.append("}"); return sb.toString(); } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/ExtensionLoaderImpl.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import org.openjdk.btrace.extension.ExtensionDescriptorDTO; import org.openjdk.btrace.extension.ExtensionLoader; import org.openjdk.btrace.extension.ExtensionRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.instrument.Instrumentation; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarFile; import java.util.stream.Collectors; /** * Manages discovery, loading, and lifecycle of BTrace extensions. */ public final class ExtensionLoaderImpl extends ExtensionLoader { private static final Logger log = LoggerFactory.getLogger(ExtensionLoaderImpl.class); private final List repositories; private final ClassLoader parentClassLoader; private final ExtensionConfig config; private final Instrumentation instrumentation; private final Map loadedExtensions; private final Map availableExtensions; /** * Create an extension loader. * * @param repositories extension repositories to scan * @param parentClassLoader parent classloader for extensions (typically BTrace boot classloader) * @param config extension configuration * @param instrumentation instrumentation instance for adding to boot classpath */ public ExtensionLoaderImpl( List repositories, ClassLoader parentClassLoader, ExtensionConfig config, Instrumentation instrumentation) { this.repositories = new ArrayList<>(repositories); this.parentClassLoader = parentClassLoader; this.config = config != null ? config : ExtensionConfig.createDefault(); this.instrumentation = instrumentation; this.loadedExtensions = new HashMap<>(); this.availableExtensions = new HashMap<>(); } /** * Discover all available extensions from configured repositories. * This should be called once during agent startup. * * @return list of discovered extensions */ @Override public List discoverExtensions() { log.info("Discovering extensions from {} repositories (config: {})", repositories.size(), config); List discovered = new ArrayList<>(); for (ExtensionRepository repository : repositories) { log.debug("Scanning repository: {}", repository.getLocation()); try { List extensions = repository.scan(); discovered.addAll(extensions); } catch (Exception e) { log.error("Failed to scan repository {}: {}", repository.getLocation(), e.getMessage(), e); } } // Resolve conflicts (higher priority repository wins, or latest version within same priority) List resolved = resolveConflicts(discovered); // Filter based on configuration (enabled/disabled lists) List filtered = new ArrayList<>(); for (ExtensionDescriptorDTO ext : resolved) { if (config.isEnabled(ext.getId())) { filtered.add(ext); } else { log.info("Extension {} disabled by configuration", ext.getId()); } } // Store in available extensions map availableExtensions.clear(); for (ExtensionDescriptorDTO ext : filtered) { availableExtensions.put(ext.getId(), ext); } log.info("Discovered {} extension(s): {}", filtered.size(), filtered.stream().map(e -> e.getId() + ":" + e.getVersion()).collect(Collectors.joining(", "))); // Load all extensions immediately to add them to bootstrap classpath // This is required so BTrace scripts can reference extension classes log.info("Loading discovered extensions to bootstrap classpath"); for (ExtensionDescriptorDTO ext : filtered) { if (!load(ext)) { log.warn("Failed to load extension {}, scripts may not be able to use it", ext.getId()); } } return filtered; } @Override public ExtensionDescriptorDTO findExtensionForService(String serviceClassName) { for (ExtensionDescriptorDTO ext : availableExtensions.values()) { if (ext.providesService(serviceClassName)) { return ext; } } return null; } /** * Load an extension and make its classes available. * This is idempotent - loading an already-loaded extension is a no-op. * * @param descriptor extension to load * @return true if loaded successfully, false otherwise */ @Override public boolean load(ExtensionDescriptorDTO descriptor) { // Synchronize on descriptor to prevent concurrent loading of the same extension synchronized (descriptor) { if (descriptor.isLoaded()) { log.debug("Extension {} is already loaded", descriptor.getId()); return true; } return doLoad(descriptor); } } private boolean doLoad(ExtensionDescriptorDTO descriptor) { log.info("Loading extension: {} version {} from {}", descriptor.getId(), descriptor.getVersion(), descriptor.getJarPath()); try { // Load any required extensions first for (String requiredId : descriptor.getRequiredExtensions()) { ExtensionDescriptorDTO required = availableExtensions.get(requiredId); if (required == null) { log.error("Required extension {} not found for {}", requiredId, descriptor.getId()); return false; } if (!load(required)) { log.error("Failed to load required extension {} for {}", requiredId, descriptor.getId()); return false; } } // Load extension from directory structure: // extensions/extension-name/ // extension-name-api.jar (added to bootstrap classpath) // extension-name-impl.jar (loaded via extension classloader) Path extensionDir = descriptor.getJarPath(); Path apiJar = findApiJar(extensionDir); Path implJar = findImplJar(extensionDir, apiJar); if (apiJar == null) { throw new IllegalStateException("No API JAR found in " + extensionDir); } if (implJar == null) { throw new IllegalStateException("No implementation JAR found in " + extensionDir); } // Add API JAR to bootstrap classpath // Note: JarFile must be closed after appendToBootstrapClassLoaderSearch as the // instrumentation API does not take ownership of the file handle try (JarFile apiJarFile = new JarFile(apiJar.toFile())) { instrumentation.appendToBootstrapClassLoaderSearch(apiJarFile); log.debug("Added {} to bootstrap classpath", apiJar.getFileName()); } // Create classloader for implementation JAR URL implUrl = implJar.toUri().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[] {implUrl}, parentClassLoader); descriptor.setClassLoader(classLoader); loadedExtensions.put(descriptor.getId(), descriptor); log.info("Successfully loaded extension: {} version {} (api: {}, impl: {})", descriptor.getId(), descriptor.getVersion(), apiJar.getFileName(), implJar.getFileName()); return true; } catch (Exception e) { log.error( "Failed to load extension {}: {}", descriptor.getId(), e.getMessage(), e); return false; } } /** * Ensure the extension API JAR is appended to the bootstrap classpath without * attempting to load the implementation JAR. This enables BTrace to generate * shims against the API when implementation use is blocked (e.g., permissions). * * @param descriptor the extension descriptor * @return true if the API JAR was found and appended; false otherwise */ @Override public boolean ensureApiOnBootstrap(ExtensionDescriptorDTO descriptor) { try { Path extensionDir = descriptor.getJarPath(); Path apiJar = findApiJar(extensionDir); if (apiJar == null) { log.warn("No API JAR found for extension {} in {}", descriptor.getId(), extensionDir); return false; } // Note: JarFile must be closed after appendToBootstrapClassLoaderSearch as the // instrumentation API does not take ownership of the file handle try (JarFile apiJarFile = new JarFile(apiJar.toFile())) { instrumentation.appendToBootstrapClassLoaderSearch(apiJarFile); log.debug("Ensured API on bootstrap for extension {} via {}", descriptor.getId(), apiJar.getFileName()); } return true; } catch (Exception e) { log.warn("Failed to ensure API on bootstrap for {}: {}", descriptor.getId(), e.getMessage(), e); return false; } } /** * Find the API JAR in the extension directory. */ private java.nio.file.Path findApiJar(java.nio.file.Path extensionDir) throws java.io.IOException { try (java.nio.file.DirectoryStream stream = java.nio.file.Files.newDirectoryStream(extensionDir, "*-api.jar")) { for (java.nio.file.Path path : stream) { return path; } } return null; } /** * Find the implementation JAR. First try reading from API JAR manifest, * then fall back to scanning directory. */ private java.nio.file.Path findImplJar(java.nio.file.Path extensionDir, java.nio.file.Path apiJar) throws java.io.IOException { if (apiJar != null) { try (java.util.jar.JarFile jar = new java.util.jar.JarFile(apiJar.toFile())) { java.util.jar.Manifest manifest = jar.getManifest(); if (manifest != null) { String implJarName = manifest.getMainAttributes().getValue("BTrace-Extension-Impl"); if (implJarName != null) { java.nio.file.Path implPath = extensionDir.resolve(implJarName); if (java.nio.file.Files.exists(implPath)) { return implPath; } } } } } // Fallback: scan directory for *-impl.jar try (java.nio.file.DirectoryStream stream = java.nio.file.Files.newDirectoryStream(extensionDir, "*-impl.jar")) { for (java.nio.file.Path path : stream) { return path; } } return null; } /** * Get all loaded extensions. * * @return collection of loaded extension descriptors */ public Collection getLoadedExtensions() { return new ArrayList<>(loadedExtensions.values()); } /** * Get all available (discovered) extensions. * * @return collection of available extension descriptors */ @Override public Collection getAvailableExtensions() { return new ArrayList<>(availableExtensions.values()); } /** * Get a specific extension by ID. * * @param extensionId extension identifier * @return extension descriptor, or null if not found */ public ExtensionDescriptorDTO getExtension(String extensionId) { return availableExtensions.get(extensionId); } /** * Resolve conflicts when multiple versions of the same extension are discovered. * Resolution strategy: * 1. Higher priority repository wins * 2. Within same priority, latest version wins * * @param discovered list of all discovered extensions * @return list of extensions with conflicts resolved */ private List resolveConflicts(List discovered) { // Group by extension ID Map> byId = new HashMap<>(); for (ExtensionDescriptorDTO ext : discovered) { byId.computeIfAbsent(ext.getId(), k -> new ArrayList<>()).add(ext); } List resolved = new ArrayList<>(); for (Map.Entry> entry : byId.entrySet()) { List candidates = entry.getValue(); if (candidates.size() == 1) { resolved.add(candidates.get(0)); continue; } // Multiple versions - resolve conflict log.debug("Resolving conflict for extension {}: {} candidates", entry.getKey(), candidates.size()); ExtensionDescriptorDTO winner = candidates.stream() .max( Comparator.comparingInt((ExtensionDescriptorDTO e) -> e.getRepository().getPriority()) .thenComparing(ExtensionDescriptorDTO::getVersion)) .orElse(null); if (winner != null) { log.info("Selected extension {} version {} from {} (priority {})", winner.getId(), winner.getVersion(), winner.getRepository().getLocation(), winner.getRepository().getPriority()); resolved.add(winner); } } return resolved; } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/ExtensionMetadata.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.PermissionSet; import org.openjdk.btrace.extension.ExtensionDescriptorDTO; import org.openjdk.btrace.extension.ExtensionRepository; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; /** * Parser for extension metadata from MANIFEST.MF attributes, * btrace-extension.properties (legacy), and META-INF/services files. */ final class ExtensionMetadata { private static final String METADATA_FILE = "META-INF/btrace-extension.properties"; private static final String SERVICES_DIR = "META-INF/services/"; // MANIFEST.MF attribute names private static final String ATTR_EXTENSION_ID = "BTrace-Extension-Id"; private static final String ATTR_EXTENSION_VERSION = "BTrace-Extension-Version"; private static final String ATTR_EXTENSION_NAME = "BTrace-Extension-Name"; private static final String ATTR_EXTENSION_DESC = "BTrace-Extension-Description"; private static final String ATTR_API_VERSION = "BTrace-API-Version"; private static final String ATTR_JAVA_VERSION = "BTrace-Java-Version"; private static final String ATTR_SERVICES = "BTrace-Extension-Services"; private static final String ATTR_REQUIRES = "BTrace-Extension-Requires"; private static final String ATTR_PERMISSIONS = "BTrace-Extension-Permissions"; private ExtensionMetadata() {} /** * Parse extension metadata from a JAR file. * Priority: MANIFEST.MF attributes > btrace-extension.properties > inference * * @param jarPath path to extension JAR * @param repository repository this extension was discovered in * @return extension descriptor, or null if not a valid extension */ static ExtensionDescriptorDTO parse(Path jarPath, ExtensionRepository repository) { return parse(jarPath, jarPath, repository); } /** * Parse extension metadata from an API JAR in an extension directory. * Priority: MANIFEST.MF attributes > btrace-extension.properties > inference * * @param apiJarPath path to extension API JAR * @param extensionDirPath path to extension directory (used as jarPath in descriptor) * @param repository repository this extension was discovered in * @return extension descriptor, or null if not a valid extension */ static ExtensionDescriptorDTO parse(Path apiJarPath, Path extensionDirPath, ExtensionRepository repository) { try (JarFile jar = new JarFile(apiJarPath.toFile())) { // Try MANIFEST.MF first ExtensionDescriptorDTO fromManifest = parseFromManifest(extensionDirPath, jar, repository); if (fromManifest != null) { return fromManifest; } // Fall back to btrace-extension.properties ExtensionDescriptorDTO fromProperties = parseFromProperties(extensionDirPath, jar, repository); if (fromProperties != null) { return fromProperties; } // Last resort: infer from JAR name and services return inferMetadata(extensionDirPath, jar, repository); } catch (IOException e) { return null; } } /** * Parse extension metadata from MANIFEST.MF attributes. */ private static ExtensionDescriptorDTO parseFromManifest( Path jarPath, JarFile jar, ExtensionRepository repository) throws IOException { Manifest manifest = jar.getManifest(); if (manifest == null) { return null; } Attributes attrs = manifest.getMainAttributes(); String id = attrs.getValue(ATTR_EXTENSION_ID); String version = attrs.getValue(ATTR_EXTENSION_VERSION); if (id == null || version == null) { // Not a BTrace extension (missing required attributes) return null; } // Parse services from both manifest attribute and META-INF/services List services = new ArrayList<>(); String servicesAttr = attrs.getValue(ATTR_SERVICES); if (servicesAttr != null && !servicesAttr.trim().isEmpty()) { services.addAll(parseList(servicesAttr)); } // Also scan META-INF/services directory services.addAll(scanServicesDirectory(jar)); return new ExtensionDescriptorDTO.Builder() .id(id) .version(version) .name(attrs.getValue(ATTR_EXTENSION_NAME) != null ? attrs.getValue(ATTR_EXTENSION_NAME) : id) .description(attrs.getValue(ATTR_EXTENSION_DESC) != null ? attrs.getValue(ATTR_EXTENSION_DESC) : "") .jarPath(jarPath) .btraceApiVersion(attrs.getValue(ATTR_API_VERSION) != null ? attrs.getValue(ATTR_API_VERSION) : "2.0+") .javaVersion(attrs.getValue(ATTR_JAVA_VERSION) != null ? attrs.getValue(ATTR_JAVA_VERSION) : "8+") .services(services) .requiredExtensions(parseList(attrs.getValue(ATTR_REQUIRES))) .requiredPermissions(parsePermissions(attrs.getValue(ATTR_PERMISSIONS))) .repository(repository) .build(); } /** * Parse extension metadata from btrace-extension.properties file (legacy). */ private static ExtensionDescriptorDTO parseFromProperties( Path jarPath, JarFile jar, ExtensionRepository repository) throws IOException { Properties props = loadMetadata(jar); if (props == null) { return null; } String id = props.getProperty("extension.id"); String version = props.getProperty("extension.version"); if (id == null || version == null) { // Invalid metadata return null; } List services = parseServices(jar, props); return new ExtensionDescriptorDTO.Builder() .id(id) .version(version) .name(props.getProperty("extension.name", id)) .description(props.getProperty("extension.description", "")) .jarPath(jarPath) .btraceApiVersion(props.getProperty("btrace.api.version", "2.0+")) .javaVersion(props.getProperty("java.version", "8+")) .services(services) .requiredExtensions(parseList(props.getProperty("requires.extensions", ""))) .requiredPermissions(parsePermissions(props.getProperty("requires.permissions", ""))) .repository(repository) .build(); } private static Properties loadMetadata(JarFile jar) throws IOException { JarEntry entry = jar.getJarEntry(METADATA_FILE); if (entry == null) { return null; } Properties props = new Properties(); try (InputStream in = jar.getInputStream(entry)) { props.load(in); } return props; } /** * Scan META-INF/services directory for service implementations. */ private static List scanServicesDirectory(JarFile jar) throws IOException { List services = new ArrayList<>(); Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().startsWith(SERVICES_DIR) && !entry.isDirectory()) { try (InputStream in = jar.getInputStream(entry); BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { line = line.trim(); // Skip comments and empty lines if (!line.isEmpty() && !line.startsWith("#")) { // Add the implementation class if (!services.contains(line)) { services.add(line); } } } } } } return services; } private static List parseServices(JarFile jar, Properties props) throws IOException { List services = new ArrayList<>(); // First check if services are listed in metadata String servicesProp = props.getProperty("services"); if (servicesProp != null && !servicesProp.trim().isEmpty()) { services.addAll(parseList(servicesProp)); } // Also scan META-INF/services directory services.addAll(scanServicesDirectory(jar)); return services; } private static ExtensionDescriptorDTO inferMetadata( Path jarPath, JarFile jar, ExtensionRepository repository) { // Try to infer metadata from JAR name String fileName = jarPath.getFileName().toString(); if (!fileName.endsWith(".jar")) { return null; } // Remove .jar extension String baseName = fileName.substring(0, fileName.length() - 4); // Try to parse name-version pattern String id = baseName; String version = "unknown"; int dashIndex = baseName.lastIndexOf('-'); if (dashIndex > 0 && dashIndex < baseName.length() - 1) { String potentialVersion = baseName.substring(dashIndex + 1); // Check if it looks like a version (starts with digit) if (!potentialVersion.isEmpty() && Character.isDigit(potentialVersion.charAt(0))) { id = baseName.substring(0, dashIndex); version = potentialVersion; } } try { List services = scanServicesDirectory(jar); if (services.isEmpty()) { // No services found, not a valid extension return null; } return new ExtensionDescriptorDTO.Builder() .id(id) .version(version) .name(id) .description("") .jarPath(jarPath) .services(services) .repository(repository) .build(); } catch (IOException e) { return null; } } private static List parseList(String value) { if (value == null || value.trim().isEmpty()) { return new ArrayList<>(); } return Arrays.asList(value.split("[,\\s]+")); } private static PermissionSet parsePermissions(String value) { if (value == null || value.trim().isEmpty()) { return PermissionSet.empty(); } String[] parts = value.split("[,\\s]+"); EnumSet set = EnumSet.noneOf(Permission.class); for (String p : parts) { try { set.add(Permission.valueOf(p.trim())); } catch (IllegalArgumentException iae) { // ignore unknown permission names to be lenient } } if (set.isEmpty()) { return PermissionSet.empty(); } return PermissionSet.of(set.toArray(new Permission[0])); } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/FileSystemExtensionRepository.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import org.openjdk.btrace.extension.ExtensionDescriptorDTO; import org.openjdk.btrace.extension.ExtensionRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; /** * Extension repository that scans a local file system directory for extension JARs. */ public final class FileSystemExtensionRepository implements ExtensionRepository { private static final Logger log = LoggerFactory.getLogger(FileSystemExtensionRepository.class); private final Path directory; private final int priority; /** * Create a file system extension repository. * * @param directory directory to scan for extension JARs * @param priority repository priority */ public FileSystemExtensionRepository(Path directory, int priority) { this.directory = directory; this.priority = priority; } @Override public List scan() { List extensions = new ArrayList<>(); if (!Files.exists(directory)) { log.debug("Extension directory does not exist: {}", directory); return extensions; } if (!Files.isDirectory(directory)) { log.warn("Extension path is not a directory: {}", directory); return extensions; } log.debug("Scanning extension directory: {}", directory); try (DirectoryStream stream = Files.newDirectoryStream(directory, Files::isDirectory)) { for (Path extDir : stream) { try { // Look for API JAR in the extension directory Path apiJar = findApiJar(extDir); if (apiJar != null) { // Parse metadata from API JAR but use extension directory as the base path ExtensionDescriptorDTO descriptor = ExtensionMetadata.parse(apiJar, extDir, this); if (descriptor != null) { extensions.add(descriptor); log.debug("Discovered extension: {} version {} from {}", descriptor.getId(), descriptor.getVersion(), extDir.getFileName()); } } else { log.debug("Skipping directory without API JAR: {}", extDir); } } catch (Exception e) { log.warn("Failed to parse extension from {}: {}", extDir, e.getMessage()); } } } catch (IOException e) { log.error("Failed to scan extension directory {}: {}", directory, e.getMessage()); } log.info("Discovered {} extension(s) in {}", extensions.size(), directory); return extensions; } /** * Find the API JAR in an extension directory. */ private Path findApiJar(Path extensionDir) throws IOException { try (DirectoryStream stream = Files.newDirectoryStream(extensionDir, "*-api.jar")) { for (Path apiJar : stream) { return apiJar; } } return null; } @Override public String getLocation() { return directory.toString(); } @Override public int getPriority() { return priority; } @Override public String toString() { return "FileSystemExtensionRepository{" + "directory=" + directory + ", priority=" + priority + '}'; } } ================================================ FILE: btrace-extension/src/main/java/org/openjdk/btrace/extension/impl/NestedJarExtensionClassLoader.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.extension.impl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.instrument.Instrumentation; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Extension classloader that loads from nested JAR structure. * *

Extension JARs contain two nested JARs: *

    *
  • api.jar - API classes added to bootstrap classpath *
  • impl.jar - Implementation classes loaded in this classloader *
* *

This classloader extracts both nested JARs to a temp directory, adds the API JAR to * bootstrap classpath via Instrumentation, and loads implementation classes from impl.jar. */ public final class NestedJarExtensionClassLoader extends URLClassLoader { private static final Logger log = LoggerFactory.getLogger(NestedJarExtensionClassLoader.class); private final String extensionId; private final String version; private final Path apiJarPath; private final Path implJarPath; private final Path tempDir; /** * Create a nested JAR extension classloader. * * @param extensionId extension identifier * @param version extension version * @param extensionJar path to extension JAR containing api.jar and impl.jar * @param parent parent classloader (typically BTrace boot classloader) * @param instrumentation instrumentation instance for adding API to bootstrap classpath * @throws IOException if nested JARs cannot be extracted */ public NestedJarExtensionClassLoader( String extensionId, String version, Path extensionJar, ClassLoader parent, Instrumentation instrumentation) throws IOException { super(new URL[0], parent); this.extensionId = extensionId; this.version = version; // Create temp directory for this extension this.tempDir = Files.createTempDirectory("btrace-ext-" + extensionId + "-"); this.tempDir.toFile().deleteOnExit(); log.debug("Extracting nested JARs from {} to {}", extensionJar, tempDir); // Extract nested JARs try (JarFile jar = new JarFile(extensionJar.toFile())) { apiJarPath = extractNestedJar(jar, "api.jar", tempDir); implJarPath = extractNestedJar(jar, "impl.jar", tempDir); } log.debug("Extracted api.jar to {} and impl.jar to {}", apiJarPath, implJarPath); // Add API JAR to bootstrap classpath if (instrumentation != null) { JarFile apiJar = new JarFile(apiJarPath.toFile()); instrumentation.appendToBootstrapClassLoaderSearch(apiJar); log.info("Added extension API to bootstrap classpath: {} ({})", extensionId, apiJarPath.getFileName()); } else { log.warn("Instrumentation not available, cannot add API JAR to bootstrap: {}", extensionId); } // Add impl JAR to this classloader's URLs addURL(implJarPath.toUri().toURL()); log.debug("Added impl JAR to extension classloader: {}", implJarPath); } /** * Extract a nested JAR from the extension JAR. * * @param extensionJar the extension JAR file * @param entryName name of nested JAR entry (e.g., "api.jar") * @param targetDir directory to extract to * @return path to extracted JAR file * @throws IOException if extraction fails */ private Path extractNestedJar(JarFile extensionJar, String entryName, Path targetDir) throws IOException { JarEntry entry = extensionJar.getJarEntry(entryName); if (entry == null) { throw new IOException( String.format( "Nested JAR not found: %s in extension %s (%s)", entryName, extensionId, extensionJar.getName())); } Path targetFile = targetDir.resolve(entryName).normalize(); if (!targetFile.startsWith(targetDir)) { throw new IOException("Zip Slip: entry would extract outside target dir: " + entryName); } try (InputStream in = extensionJar.getInputStream(entry); OutputStream out = Files.newOutputStream(targetFile)) { byte[] buffer = new byte[8192]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } } targetFile.toFile().deleteOnExit(); log.debug("Extracted {} ({} bytes) to {}", entryName, entry.getSize(), targetFile); return targetFile; } public String getExtensionId() { return extensionId; } public String getVersion() { return version; } public Path getApiJarPath() { return apiJarPath; } public Path getImplJarPath() { return implJarPath; } @Override public String toString() { return String.format( "NestedJarExtensionClassLoader[%s:%s, api=%s, impl=%s]", extensionId, version, apiJarPath.getFileName(), implJarPath.getFileName()); } } ================================================ FILE: btrace-extension/src/test/java/org/openjdk/btrace/extension/ExtensionBridgeImplPolicyTest.java ================================================ package org.openjdk.btrace.extension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.PermissionSet; import org.openjdk.btrace.extension.impl.ExtensionBridgeImpl; import java.nio.file.Paths; import java.util.Collections; import static org.junit.jupiter.api.Assertions.*; class ExtensionBridgeImplPolicyTest { private static final String EXT_ID_BASE = "test-ext"; private static final String SERVICE_IFACE = "test.ext.Service"; // with ServiceLoader provider private static final String SERVICE_IFACE2 = "test.ext2.Service2"; // conventional Impl only private static class DummyLoader extends ExtensionLoader { final ExtensionDescriptorDTO desc; boolean apiOnBoot; boolean loaded; DummyLoader(ExtensionDescriptorDTO desc) { this.desc = desc; } @Override public ExtensionDescriptorDTO findExtensionForService(String serviceClassName) { return desc; } @Override public java.util.List discoverExtensions() { return Collections.singletonList(desc); } @Override public java.util.Collection getAvailableExtensions() { return Collections.singletonList(desc); } @Override public boolean ensureApiOnBootstrap(ExtensionDescriptorDTO descriptor) { apiOnBoot = true; return true; } @Override public boolean load(ExtensionDescriptorDTO descriptor) { loaded = true; return true; } } private ExtensionDescriptorDTO.Builder baseBuilder(String serviceFqcn, String extId) { return new ExtensionDescriptorDTO.Builder() .id(extId) .version("1.0.0") .name("Test") .description("Test") .jarPath(Paths.get("/tmp/test.jar")) .services(Collections.singletonList(serviceFqcn)) .requiredExtensions(Collections.emptyList()) .repository(null) .requiredPermissions(PermissionSet.empty()); } @BeforeEach void resetPolicy() { PermissionPolicy p = PermissionPolicy.get(); p.setAllowExtensionsCsv(""); p.setDenyExtensionsCsv(""); p.setAllowPrivileged(false); } @Test void denyListTriggersFallbackAndRegistersFailure() throws Exception { String extId = EXT_ID_BASE + "-deny"; PermissionPolicy.get().setDenyExtensionsCsv(extId); ExtensionDescriptorDTO dto = baseBuilder(SERVICE_IFACE, extId).build(); DummyLoader dl = new DummyLoader(dto); ExtensionBridgeImpl bridge = new ExtensionBridgeImpl(dl); Class clz = bridge.getExtensionClass(SERVICE_IFACE); assertNull(clz, "Expected null when denied; serviceClass=" + SERVICE_IFACE + ", extId=" + extId); assertTrue(dl.apiOnBoot, "API should be ensured on bootstrap; extId=" + extId); assertFalse(dl.loaded, "Implementation must not be loaded when denied; extId=" + extId); String reason = ExtensionRegistry.getFailedExtensions().get(extId); assertEquals("Blocked by policy (denyExtensions)", reason, "Unexpected failure reason for extId=" + extId + ": " + reason); } @Test void privilegedWithoutAllowTriggersFallback() throws Exception { String extId = EXT_ID_BASE + "-priv"; PermissionSet perms = PermissionSet.of(Permission.THREADS); ExtensionDescriptorDTO dto = baseBuilder(SERVICE_IFACE, extId).requiredPermissions(perms).build(); DummyLoader dl = new DummyLoader(dto); ExtensionBridgeImpl bridge = new ExtensionBridgeImpl(dl); Class clz = bridge.getExtensionClass(SERVICE_IFACE); assertNull(clz, "Expected null when privileged extension is not allowed; extId=" + extId); assertTrue(dl.apiOnBoot, "API should be ensured on bootstrap for privileged fallback; extId=" + extId); assertFalse(dl.loaded, "Privileged implementation should not be loaded when not allowed; extId=" + extId); String reason = ExtensionRegistry.getFailedExtensions().get(extId); assertNotNull(reason, "Expected a failure reason to be recorded; extId=" + extId); assertTrue(reason.startsWith("Blocked privileged extension."), "Unexpected failure reason for extId=" + extId + ": " + reason); } @Test void allowedLoadsAndFindsConventionalImpl() throws Exception { // Allow privileged to ensure no policy block PermissionPolicy.get().setAllowPrivileged(true); String extId = EXT_ID_BASE + "-conv"; ExtensionDescriptorDTO dto = baseBuilder(SERVICE_IFACE2, extId).build(); DummyLoader dl = new DummyLoader(dto); // Use current classloader which has test classes dto.setClassLoader(this.getClass().getClassLoader()); ExtensionBridgeImpl bridge = new ExtensionBridgeImpl(dl); // Resolve conventional Impl for SERVICE_IFACE2 Class impl2 = bridge.getExtensionClass(SERVICE_IFACE2); assertNotNull(impl2, "Expected conventional Impl to be resolved; service=" + SERVICE_IFACE2 + ", extId=" + extId + ", loaded=" + dl.loaded); assertEquals("test.ext2.Service2Impl", impl2.getName(), "Resolved class name mismatch; got=" + (impl2 != null ? impl2.getName() : "null")); } @Test void allowedLoadsAndFindsServiceLoaderProvider() throws Exception { PermissionPolicy.get().setAllowPrivileged(true); String extId = EXT_ID_BASE + "-spi"; ExtensionDescriptorDTO dto = baseBuilder(SERVICE_IFACE, extId).build(); DummyLoader dl = new DummyLoader(dto); dto.setClassLoader(this.getClass().getClassLoader()); ExtensionBridgeImpl bridge = new ExtensionBridgeImpl(dl); Class impl = bridge.getExtensionClass(SERVICE_IFACE); assertNotNull(impl, "Expected SPI provider to be resolved; service=" + SERVICE_IFACE + ", extId=" + extId + ", loaded=" + dl.loaded); assertEquals("test.ext.SpiImpl", impl.getName(), "SPI provider class name mismatch: " + (impl != null ? impl.getName() : "null")); } } ================================================ FILE: btrace-extension/src/test/java/org/openjdk/btrace/extension/ExtensionLoaderImplConcurrencyTest.java ================================================ package org.openjdk.btrace.extension; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.openjdk.btrace.core.extensions.PermissionSet; import org.openjdk.btrace.extension.impl.ExtensionConfig; import org.openjdk.btrace.extension.impl.ExtensionLoaderImpl; import java.io.IOException; import java.lang.instrument.ClassDefinition; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class ExtensionLoaderImplConcurrencyTest { @TempDir Path tempDir; @Test void loadIsIdempotentUnderConcurrency() throws Exception { Path extDir = tempDir.resolve("test-ext"); Files.createDirectories(extDir); String implName = "test-ext-impl.jar"; Path apiJar = extDir.resolve("test-ext-api.jar"); Path implJar = extDir.resolve(implName); writeJar(apiJar, implName); writeJar(implJar, null); DummyInstrumentation instrumentation = new DummyInstrumentation(); ExtensionLoaderImpl loader = new ExtensionLoaderImpl( Collections.emptyList(), getClass().getClassLoader(), ExtensionConfig.createDefault(), instrumentation); ExtensionDescriptorDTO descriptor = new ExtensionDescriptorDTO.Builder() .id("test-ext") .version("1.0.0") .name("Test") .description("Test") .jarPath(extDir) .services(List.of()) .requiredExtensions(List.of()) .repository(null) .requiredPermissions(PermissionSet.empty()) .build(); int threads = 8; CountDownLatch start = new CountDownLatch(1); CountDownLatch done = new CountDownLatch(threads); AtomicInteger success = new AtomicInteger(); ExecutorService executor = Executors.newFixedThreadPool(threads); try { for (int i = 0; i < threads; i++) { executor.execute( () -> { try { start.await(); if (loader.load(descriptor)) { success.incrementAndGet(); } } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } finally { done.countDown(); } }); } start.countDown(); assertTrue(done.await(5, TimeUnit.SECONDS), "Load operations did not finish in time"); } finally { executor.shutdownNow(); } assertEquals(threads, success.get()); assertEquals(1, instrumentation.appendCount.get()); assertTrue(descriptor.isLoaded()); assertNotNull(descriptor.getClassLoader()); assertEquals(1, loader.getLoadedExtensions().size()); } private static void writeJar(Path jarPath, String implJarName) throws IOException { Manifest manifest = new Manifest(); Attributes attrs = manifest.getMainAttributes(); attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); if (implJarName != null) { attrs.putValue("BTrace-Extension-Impl", implJarName); } try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(jarPath), manifest)) { // empty jar is sufficient for loader tests } } private static final class DummyInstrumentation implements Instrumentation { private final AtomicInteger appendCount = new AtomicInteger(); @Override public void addTransformer(ClassFileTransformer transformer, boolean canRetransform) { } @Override public void addTransformer(ClassFileTransformer transformer) { } @Override public boolean removeTransformer(ClassFileTransformer transformer) { return false; } @Override public boolean isRetransformClassesSupported() { return false; } @Override public void retransformClasses(Class... classes) { } @Override public boolean isRedefineClassesSupported() { return false; } @Override public void redefineClasses(ClassDefinition... definitions) { } @Override public boolean isModifiableClass(Class theClass) { return false; } @Override public Class[] getAllLoadedClasses() { return new Class[0]; } @Override public Class[] getInitiatedClasses(ClassLoader loader) { return new Class[0]; } @Override public long getObjectSize(Object objectToSize) { return 0L; } @Override public void appendToBootstrapClassLoaderSearch(JarFile jarfile) { appendCount.incrementAndGet(); } @Override public void appendToSystemClassLoaderSearch(JarFile jarfile) { } @Override public boolean isNativeMethodPrefixSupported() { return false; } @Override public void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) { } @Override public void redefineModule( Module module, Set extraReads, Map> extraExports, Map> extraOpens, Set> extraUses, Map, List>> extraProvides) { } @Override public boolean isModifiableModule(Module module) { return false; } } } ================================================ FILE: btrace-extension/src/test/java/test/ext/Service.java ================================================ package test.ext; public interface Service {} ================================================ FILE: btrace-extension/src/test/java/test/ext/ServiceImpl.java ================================================ package test.ext; public class ServiceImpl implements Service {} ================================================ FILE: btrace-extension/src/test/java/test/ext/SpiImpl.java ================================================ package test.ext; public class SpiImpl implements Service {} ================================================ FILE: btrace-extension/src/test/java/test/ext2/Service2.java ================================================ package test.ext2; public interface Service2 {} ================================================ FILE: btrace-extension/src/test/java/test/ext2/Service2Impl.java ================================================ package test.ext2; public class Service2Impl implements Service2 {} ================================================ FILE: btrace-extension/src/test/resources/META-INF/services/test.ext.Service ================================================ test.ext.SpiImpl ================================================ FILE: btrace-extensions/btrace-metrics/build.gradle ================================================ plugins { id 'org.openjdk.btrace.extension' alias(libs.plugins.shadow) } java { sourceCompatibility = 8 targetCompatibility = 8 } compileJava { javaCompiler = javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(11)) } } btraceExtension { id = 'btrace-metrics' name = 'BTrace HDR Histogram' description = 'High-performance metrics using HdrHistogram for accurate percentile tracking' services = ['org.openjdk.btrace.metrics.MetricsService'] shadedPackages = [ 'org.HdrHistogram': 'org.openjdk.btrace.metrics.shaded.hdrhistogram', 'com.clearspring.analytics': 'org.openjdk.btrace.metrics.shaded.clearspring' ] // Strict lint is enabled by default; API exposes builders instead of public constructors } dependencies { // API dependencies (no legacy services API) apiCompileOnly 'org.jetbrains:annotations:26.1.0' apiCompileOnly project(':btrace-core') implCompileOnly 'org.jetbrains:annotations:26.1.0' // Implementation dependencies (will be shaded) implImplementation project(':btrace-core') implImplementation libs.hdrhistogram implImplementation libs.stream.lib testImplementation libs.junit.jupiter testImplementation libs.hdrhistogram } test { useJUnitPlatform() } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/MetricsService.java ================================================ package org.openjdk.btrace.metrics; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.ServiceDescriptor; import org.openjdk.btrace.metrics.histogram.HistogramConfig; import org.openjdk.btrace.metrics.histogram.HistogramConfigBuilder; import org.openjdk.btrace.metrics.histogram.HistogramMetric; import org.openjdk.btrace.metrics.stats.StatsMetric; /** * High-performance metrics service API. */ @ServiceDescriptor(permissions = { Permission.THREADS }) public interface MetricsService { @Nullable HistogramConfigBuilder newHistogramConfig(); @Nullable HistogramMetric histogram(@NotNull String name); @Nullable HistogramMetric histogram(@NotNull String name, @NotNull HistogramConfig config); @Nullable HistogramMetric histogram(@NotNull String name, @NotNull String key); @Nullable HistogramMetric histogram(@NotNull String name, @NotNull String key, @NotNull HistogramConfig config); // Convenience creators for common units (matches prior constants): @Nullable HistogramMetric histogramMicros(@NotNull String name); @Nullable HistogramMetric histogramMicros(@NotNull String name, @NotNull String key); @Nullable HistogramMetric histogramMillis(@NotNull String name); @Nullable HistogramMetric histogramMillis(@NotNull String name, @NotNull String key); @Nullable StatsMetric stats(@NotNull String name); @Nullable StatsMetric stats(@NotNull String name, @NotNull String key); void reset(); void clear(); int size(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/histogram/HistogramConfig.java ================================================ package org.openjdk.btrace.metrics.histogram; /** * Configuration for HdrHistogram. * *

Defines the range and precision of histogram measurements. Probes do not instantiate configs * directly; obtain a builder from the injected service and pass the built config handle back to the * service. */ public interface HistogramConfig { long getLowestDiscernibleValue(); long getHighestTrackableValue(); int getNumberOfSignificantValueDigits(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/histogram/HistogramConfigBuilder.java ================================================ package org.openjdk.btrace.metrics.histogram; /** Builder for {@link HistogramConfig}. Obtain from the injected service. */ public interface HistogramConfigBuilder { HistogramConfigBuilder lowestDiscernibleValue(long value); HistogramConfigBuilder highestTrackableValue(long value); HistogramConfigBuilder significantDigits(int digits); HistogramConfig build(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/histogram/HistogramMetric.java ================================================ package org.openjdk.btrace.metrics.histogram; import org.jetbrains.annotations.Nullable; public interface HistogramMetric { void record(long value); void recordValueWithCount(long value, long count); @Nullable HistogramSnapshot snapshot(); void reset(); @Nullable String getName(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/histogram/HistogramSnapshot.java ================================================ package org.openjdk.btrace.metrics.histogram; import org.jetbrains.annotations.Nullable; public interface HistogramSnapshot { @Nullable String getName(); long p50(); long p75(); long p90(); long p95(); long p99(); long p999(); long p9999(); long percentile(double percentile); long count(); long min(); long max(); double mean(); double stddev(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/package-info.java ================================================ @ExtensionDescriptor( name = "btrace-metrics", version = "1.0", description = "High-performance metrics APIs", permissions = { Permission.THREADS } ) package org.openjdk.btrace.metrics; import org.openjdk.btrace.core.extensions.ExtensionDescriptor; import org.openjdk.btrace.core.extensions.Permission; ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/stats/StatsMetric.java ================================================ package org.openjdk.btrace.metrics.stats; import org.jetbrains.annotations.Nullable; public interface StatsMetric { void record(long value); @Nullable StatsSnapshot snapshot(); void reset(); @Nullable String getName(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/api/java/org/openjdk/btrace/metrics/stats/StatsSnapshot.java ================================================ package org.openjdk.btrace.metrics.stats; import org.jetbrains.annotations.Nullable; public interface StatsSnapshot { @Nullable String getName(); long count(); long sum(); long min(); long max(); double mean(); double stddev(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/Metric.java ================================================ package org.openjdk.btrace.metrics; import org.jetbrains.annotations.NotNull; /** * Base interface for all metrics. */ public interface Metric { /** * Get the metric name. * * @return metric name */ @NotNull String getName(); /** * Reset the metric to initial state. */ void reset(); } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/MetricsServiceImpl.java ================================================ package org.openjdk.btrace.metrics; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.openjdk.btrace.core.extensions.Extension; import org.openjdk.btrace.metrics.histogram.HistogramConfig; import org.openjdk.btrace.metrics.histogram.HistogramConfigBuilder; import org.openjdk.btrace.metrics.histogram.HistogramConfigBuilderImpl; import org.openjdk.btrace.metrics.histogram.HistogramConfigImpl; import org.openjdk.btrace.metrics.histogram.HistogramMetric; import org.openjdk.btrace.metrics.histogram.HistogramMetricImpl; import org.openjdk.btrace.metrics.registry.MetricRegistry; import org.openjdk.btrace.metrics.stats.StatsMetric; import org.openjdk.btrace.metrics.stats.StatsMetricImpl; /** * High-performance metrics service built on HdrHistogram. * *

Provides: - Histograms with accurate percentile queries (p50, p95, p99, etc.) - Basic * statistics (count, sum, min, max, avg, stddev) * *

Thread-safe and designed for low-latency hot paths. */ public final class MetricsServiceImpl extends Extension implements MetricsService { private final MetricRegistry registry = new MetricRegistry(); public MetricsServiceImpl() {} // ========== Histogram Creation ========== @Override public HistogramConfigBuilder newHistogramConfig() { return new HistogramConfigBuilderImpl(); } /** * Create histogram with default configuration. * * @param name metric name * @return histogram metric */ @Nullable public HistogramMetric histogram(@NotNull String name) { // Default: 1ns .. 1h, 3 significant digits return histogram(name, new HistogramConfigImpl(1L, 3_600_000_000_000L, 3)); } /** * Create histogram with custom configuration. * * @param name metric name * @param config histogram configuration * @return histogram metric */ @Nullable public HistogramMetric histogram(@NotNull String name, @NotNull HistogramConfig config) { return registry.getOrCreate(name, null, () -> new HistogramMetricImpl(name, config)); } /** * Create grouped histogram. * * @param name metric name * @param key grouping key * @return histogram metric */ @Nullable public HistogramMetric histogram(@NotNull String name, @NotNull String key) { return registry.getOrCreate(name, key, () -> new HistogramMetricImpl(name, new org.openjdk.btrace.metrics.histogram.HistogramConfigImpl(1L, 3_600_000_000_000L, 3))); } // Convenience creators for micros/millis ranges (match prior constants) @Override @Nullable public HistogramMetric histogramMicros(@NotNull String name) { return histogram(name, new HistogramConfigImpl(1L, 60_000_000L, 3)); } @Override @Nullable public HistogramMetric histogramMicros(@NotNull String name, @NotNull String key) { return registry.getOrCreate(name, key, () -> new HistogramMetricImpl(name, new HistogramConfigImpl(1L, 60_000_000L, 3))); } @Override @Nullable public HistogramMetric histogramMillis(@NotNull String name) { return histogram(name, new HistogramConfigImpl(1L, 600_000L, 3)); } @Override @Nullable public HistogramMetric histogramMillis(@NotNull String name, @NotNull String key) { return registry.getOrCreate(name, key, () -> new HistogramMetricImpl(name, new HistogramConfigImpl(1L, 600_000L, 3))); } /** * Create grouped histogram with custom configuration. * * @param name metric name * @param key grouping key * @param config histogram configuration * @return histogram metric */ @Nullable public HistogramMetric histogram(@NotNull String name, @NotNull String key, @NotNull HistogramConfig config) { return registry.getOrCreate(name, key, () -> new HistogramMetricImpl(name, config)); } // ========== Statistics Creation ========== /** * Create statistics metric (count/sum/min/max/avg/stddev). * * @param name metric name * @return statistics metric */ @Nullable public StatsMetric stats(@NotNull String name) { return registry.getOrCreate(name, null, () -> new StatsMetricImpl(name)); } /** * Create grouped statistics metric. * * @param name metric name * @param key grouping key * @return statistics metric */ @Nullable public StatsMetric stats(@NotNull String name, @NotNull String key) { return registry.getOrCreate(name, key, () -> new org.openjdk.btrace.metrics.stats.StatsMetricImpl(name)); } // ========== Query Operations ========== /** * Reset all metrics. */ public void reset() { registry.reset(); } /** * Clear all metrics. */ public void clear() { registry.clear(); } /** * Get number of registered metrics. * * @return metric count */ public int size() { return registry.size(); } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/histogram/HistogramConfigBuilderImpl.java ================================================ package org.openjdk.btrace.metrics.histogram; public final class HistogramConfigBuilderImpl implements HistogramConfigBuilder { private long lowest = 1L; private long highest = 3_600_000_000_000L; // 1 hour in nanos by default private int digits = 3; @Override public HistogramConfigBuilder lowestDiscernibleValue(long value) { this.lowest = value; return this; } @Override public HistogramConfigBuilder highestTrackableValue(long value) { this.highest = value; return this; } @Override public HistogramConfigBuilder significantDigits(int digits) { this.digits = digits; return this; } @Override public HistogramConfig build() { return new HistogramConfigImpl(lowest, highest, digits); } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/histogram/HistogramConfigImpl.java ================================================ package org.openjdk.btrace.metrics.histogram; public final class HistogramConfigImpl implements HistogramConfig { private final long lowestDiscernibleValue; private final long highestTrackableValue; private final int numberOfSignificantValueDigits; public HistogramConfigImpl(long lowest, long highest, int digits) { this.lowestDiscernibleValue = lowest; this.highestTrackableValue = highest; this.numberOfSignificantValueDigits = digits; } @Override public long getLowestDiscernibleValue() { return lowestDiscernibleValue; } @Override public long getHighestTrackableValue() { return highestTrackableValue; } @Override public int getNumberOfSignificantValueDigits() { return numberOfSignificantValueDigits; } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/histogram/HistogramMetricImpl.java ================================================ package org.openjdk.btrace.metrics.histogram; import org.HdrHistogram.Histogram; import org.HdrHistogram.Recorder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.openjdk.btrace.metrics.Metric; /** * Histogram metric using HdrHistogram for accurate percentiles. * *

Uses Recorder pattern for lock-free writes in hot path. */ public final class HistogramMetricImpl implements HistogramMetric, Metric { private final String name; private final Recorder recorder; private final HistogramConfig config; public HistogramMetricImpl(String name, HistogramConfig config) { this.name = name; this.config = config; this.recorder = new Recorder( config.getLowestDiscernibleValue(), config.getHighestTrackableValue(), config.getNumberOfSignificantValueDigits()); } /** * Record value - ZERO ALLOCATION in hot path. * * @param value value to record */ public void record(long value) { recorder.recordValue(value); } /** * Record value with count - ZERO ALLOCATION. * * @param value value to record * @param count number of times to record */ public void recordValueWithCount(long value, long count) { recorder.recordValueWithCount(value, count); } /** * Get snapshot for querying percentiles. * *

This allocates a new Histogram via getIntervalHistogram(). Call infrequently (e.g., on * OnEvent). * * @return immutable snapshot */ @Nullable public HistogramSnapshot snapshot() { Histogram histogram = recorder.getIntervalHistogram(); return new HistogramSnapshotImpl(name, histogram); } @Override public void reset() { recorder.reset(); } @Override @NotNull public String getName() { return name; } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/histogram/HistogramSnapshotImpl.java ================================================ package org.openjdk.btrace.metrics.histogram; import org.HdrHistogram.Histogram; import org.jetbrains.annotations.NotNull; /** * Immutable snapshot of histogram for percentile queries. */ public final class HistogramSnapshotImpl implements HistogramSnapshot { private final String name; private final Histogram histogram; HistogramSnapshotImpl(String name, Histogram histogram) { this.name = name; this.histogram = histogram; } @NotNull public String getName() { return name; } // ========== Percentiles ========== public long p50() { return histogram.getValueAtPercentile(50.0); } public long p75() { return histogram.getValueAtPercentile(75.0); } public long p90() { return histogram.getValueAtPercentile(90.0); } public long p95() { return histogram.getValueAtPercentile(95.0); } public long p99() { return histogram.getValueAtPercentile(99.0); } public long p999() { return histogram.getValueAtPercentile(99.9); } public long p9999() { return histogram.getValueAtPercentile(99.99); } public long percentile(double percentile) { return histogram.getValueAtPercentile(percentile); } // ========== Statistics ========== public long count() { return histogram.getTotalCount(); } public long min() { return histogram.getMinValue(); } public long max() { return histogram.getMaxValue(); } public double mean() { return histogram.getMean(); } public double stddev() { return histogram.getStdDeviation(); } // ========== Access to raw histogram ========== public Histogram getHistogram() { return histogram; } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/registry/MetricRegistry.java ================================================ package org.openjdk.btrace.metrics.registry; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import org.openjdk.btrace.metrics.Metric; /** * Thread-safe registry for storing metrics. * *

Supports grouping via String keys. */ public final class MetricRegistry { private final ConcurrentHashMap metrics = new ConcurrentHashMap<>(); /** * Get or create a metric. * * @param name metric name * @param key grouping key (null for ungrouped) * @param factory factory to create metric if not present * @param metric type * @return metric instance */ @SuppressWarnings("unchecked") public T getOrCreate(String name, String key, Supplier factory) { String fullName = makeFullName(name, key); return (T) metrics.computeIfAbsent(fullName, k -> factory.get()); } /** * Reset all metrics. */ public void reset() { for (Metric metric : metrics.values()) { metric.reset(); } } /** * Get number of registered metrics. * * @return metric count */ public int size() { return metrics.size(); } /** * Clear all metrics. */ public void clear() { metrics.clear(); } private String makeFullName(String name, String key) { if (key == null || key.isEmpty()) { return name; } return name + ":" + key; } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/stats/StatsMetricImpl.java ================================================ package org.openjdk.btrace.metrics.stats; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.openjdk.btrace.metrics.Metric; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; /** * Statistics metric using lock-free atomics. * *

Provides: count, sum, min, max, average, stddev * *

Uses LongAdder for count/sum (better than AtomicLong under contention). Uses AtomicLong for * min/max with CAS loop. */ public final class StatsMetricImpl implements StatsMetric, Metric { private final String name; private final LongAdder count = new LongAdder(); private final LongAdder sum = new LongAdder(); private final LongAdder sumOfSquares = new LongAdder(); private final AtomicLong min = new AtomicLong(Long.MAX_VALUE); private final AtomicLong max = new AtomicLong(Long.MIN_VALUE); public StatsMetricImpl(String name) { this.name = name; } /** * Record value - lock-free. * * @param value value to record */ public void record(long value) { count.increment(); sum.add(value); sumOfSquares.add(value * value); // Update min (CAS loop) long currentMin; do { currentMin = min.get(); if (value >= currentMin) { break; } } while (!min.compareAndSet(currentMin, value)); // Update max (CAS loop) long currentMax; do { currentMax = max.get(); if (value <= currentMax) { break; } } while (!max.compareAndSet(currentMax, value)); } /** * Get immutable snapshot. * * @return snapshot */ @Nullable public StatsSnapshot snapshot() { long cnt = count.sum(); long sm = sum.sum(); long sumSq = sumOfSquares.sum(); long mn = min.get(); long mx = max.get(); if (cnt == 0) { return new StatsSnapshotImpl(name, 0, 0, 0, 0, 0, 0); } double avg = (double) sm / cnt; // Variance = E[X^2] - E[X]^2 double variance = ((double) sumSq / cnt) - (avg * avg); double stddev = Math.sqrt(Math.max(0, variance)); return new StatsSnapshotImpl(name, cnt, sm, mn, mx, avg, stddev); } @Override public void reset() { count.reset(); sum.reset(); sumOfSquares.reset(); min.set(Long.MAX_VALUE); max.set(Long.MIN_VALUE); } @Override @NotNull public String getName() { return name; } } ================================================ FILE: btrace-extensions/btrace-metrics/src/impl/java/org/openjdk/btrace/metrics/stats/StatsSnapshotImpl.java ================================================ package org.openjdk.btrace.metrics.stats; import org.jetbrains.annotations.NotNull; /** * Immutable snapshot of statistics. */ public final class StatsSnapshotImpl implements StatsSnapshot { private final String name; private final long count; private final long sum; private final long min; private final long max; private final double mean; private final double stddev; public StatsSnapshotImpl( String name, long count, long sum, long min, long max, double mean, double stddev) { this.name = name; this.count = count; this.sum = sum; this.min = min; this.max = max; this.mean = mean; this.stddev = stddev; } @NotNull public String getName() { return name; } public long count() { return count; } public long sum() { return sum; } public long min() { return min; } public long max() { return max; } public double mean() { return mean; } public double stddev() { return stddev; } } ================================================ FILE: btrace-extensions/btrace-metrics/src/test/java/org/openjdk/btrace/metrics/HistogramMetricTest.java ================================================ package org.openjdk.btrace.metrics; import org.junit.jupiter.api.Test; import org.openjdk.btrace.metrics.histogram.HistogramConfigImpl; import org.openjdk.btrace.metrics.histogram.HistogramMetric; import org.openjdk.btrace.metrics.histogram.HistogramMetricImpl; import org.openjdk.btrace.metrics.histogram.HistogramSnapshot; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class HistogramMetricTest { @Test void recordsAndSnapshots() { HistogramMetric m = new HistogramMetricImpl("h", new HistogramConfigImpl(1L, 600_000L, 3)); m.record(10); m.record(20); HistogramSnapshot snap = m.snapshot(); assertEquals("h", snap.getName()); assertTrue(snap.count() >= 2); assertTrue(snap.max() >= 20); } } ================================================ FILE: btrace-extensions/btrace-metrics/src/test/java/org/openjdk/btrace/metrics/StatsMetricTest.java ================================================ package org.openjdk.btrace.metrics; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.openjdk.btrace.metrics.stats.StatsMetric; import org.openjdk.btrace.metrics.stats.StatsSnapshot; class StatsMetricTest { @Test void accumulatesAndSnapshots() { StatsMetric m = new org.openjdk.btrace.metrics.stats.StatsMetricImpl("s"); m.record(1); m.record(3); StatsSnapshot snap = m.snapshot(); assertEquals("s", snap.getName()); assertEquals(2, snap.count()); assertEquals(4, snap.sum()); assertEquals(1, snap.min()); assertEquals(3, snap.max()); } } ================================================ FILE: btrace-extensions/btrace-statsd/build.gradle ================================================ plugins { id 'org.openjdk.btrace.extension' alias(libs.plugins.shadow) } java { sourceCompatibility = 8 targetCompatibility = 8 } compileJava { javaCompiler = javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(11)) } } btraceExtension { id = 'btrace-statsd' name = 'BTrace StatsD' description = 'StatsD metrics client for BTrace' services = ['org.openjdk.btrace.statsd.Statsd'] } dependencies { apiCompileOnly project(':btrace-core') implImplementation project(':btrace-core') implementation libs.slf4j } ================================================ FILE: btrace-extensions/btrace-statsd/src/api/java/org/openjdk/btrace/statsd/Statsd.java ================================================ package org.openjdk.btrace.statsd; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.core.extensions.ServiceDescriptor; /** API for StatsD metrics client. */ @ServiceDescriptor(permissions = { Permission.NETWORK }) public interface Statsd { void increment(String name); void increment(String name, String tags); } ================================================ FILE: btrace-extensions/btrace-statsd/src/api/java/org/openjdk/btrace/statsd/package-info.java ================================================ @ExtensionDescriptor( name = "btrace-statsd", version = "1.0", description = "StatsD metrics APIs", permissions = { Permission.NETWORK } ) package org.openjdk.btrace.statsd; import org.openjdk.btrace.core.extensions.ExtensionDescriptor; import org.openjdk.btrace.core.extensions.Permission; ================================================ FILE: btrace-extensions/btrace-statsd/src/impl/java/org/openjdk/btrace/statsd/StatsdImpl.java ================================================ package org.openjdk.btrace.statsd; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.nio.charset.StandardCharsets; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.core.extensions.Extension; import org.openjdk.btrace.core.extensions.Permission; public final class StatsdImpl extends Extension implements Statsd { @Override public void increment(String name) { increment(name, null); } @Override public void increment(String name, String tags) { try { StringBuilder sb = new StringBuilder(); sb.append(name).append(":1|c"); if (tags != null && !tags.isEmpty()) { sb.append("|#").append(tags); } byte[] data = sb.toString().getBytes(StandardCharsets.US_ASCII); DatagramPacket pkt = new DatagramPacket(data, data.length); pkt.setAddress(InetAddress.getByName(SharedSettings.GLOBAL.getStatsdHost())); pkt.setPort(SharedSettings.GLOBAL.getStatsdPort()); try (DatagramSocket socket = new DatagramSocket()) { socket.send(pkt); } } catch (Throwable ignore) { // Best-effort, ignore errors in script path } } } ================================================ FILE: btrace-extensions/btrace-utils/build.gradle ================================================ plugins { id 'org.openjdk.btrace.extension' alias(libs.plugins.shadow) } java { sourceCompatibility = 8 targetCompatibility = 8 } compileJava { javaCompiler = javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(11)) } } btraceExtension { id = 'btrace-utils' name = 'BTrace Utilities' description = 'Utility services for BTrace scripts (eg. printer)' services = ['org.openjdk.btrace.utils.PrinterService'] } dependencies { // API dependencies (minimal) - no legacy services API apiCompileOnly project(':btrace-core') implImplementation project(':btrace-core') } ================================================ FILE: btrace-extensions/btrace-utils/src/api/java/org/openjdk/btrace/utils/PrinterService.java ================================================ package org.openjdk.btrace.utils; import org.openjdk.btrace.core.extensions.ServiceDescriptor; /** * Simple printer service API for BTrace scripts. */ @ServiceDescriptor public interface PrinterService { void print(String s); void println(String s); void println(); } ================================================ FILE: btrace-extensions/btrace-utils/src/api/java/org/openjdk/btrace/utils/package-info.java ================================================ @ExtensionDescriptor( name = "btrace-utils", version = "1.0", description = "Utility helper APIs for BTrace scripts" ) package org.openjdk.btrace.utils; import org.openjdk.btrace.core.extensions.ExtensionDescriptor; ================================================ FILE: btrace-extensions/btrace-utils/src/impl/java/org/openjdk/btrace/utils/PrinterServiceImpl.java ================================================ package org.openjdk.btrace.utils; import org.openjdk.btrace.core.extensions.Extension; public final class PrinterServiceImpl extends Extension implements PrinterService { private static final String NL = System.getProperty("line.separator"); @Override public void print(String s) { if (s != null) { getContext().send(s); } } @Override public void println(String s) { print(s + NL); } @Override public void println() { print(NL); } @Override public void close() { if (getContext().getArgs().containsKey("extensionCloseTest")) { getContext().send("extension close: btrace-utils"); } } } ================================================ FILE: btrace-extensions/build.gradle ================================================ plugins { id 'base' } // Aggregate task to build API JARs of all contained extensions tasks.register('buildExtensionsApi') { group = 'build' description = 'Build API JARs for all extensions in this composite project.' } // Wire all child projects that expose a buildApiJar task gradle.projectsEvaluated { tasks.named('buildExtensionsApi').configure { subprojects.each { sp -> def apiTask = sp.tasks.findByName('buildApiJar') if (apiTask != null) { dependsOn(apiTask) } } } } ================================================ FILE: btrace-gradle-plugin/README.md ================================================ BTrace Gradle Extension Plugin Overview - Gradle plugin to build and package BTrace extensions with sane defaults. - Scans implementation bytecode to infer minimal required permissions and writes them into the API JAR manifest. - Produces three artifacts: API JAR (`classifier=api`), Impl JAR (`classifier=impl`, shadowed), and a distributable ZIP (`classifier=extension`). Apply The Plugin ``` plugins { id("org.openjdk.btrace.extension") version "${btraceVersion}" } repositories { mavenCentral() mavenLocal() // if you published locally for testing } ``` Project Layout - `src/api/java`, `src/api/resources`: public API package visible to BTrace scripts and the agent (ends up on bootstrap). - `src/impl/java`, `src/impl/resources`: implementation package shadowed and isolated behind the API manifest. DSL (extension `btraceExtension`) ``` btraceExtension { id = "com.example.myext" // required: globally unique extension ID name = "My Extension" // optional description = "Does things" // optional // Optional: omit to auto-detect from @ServiceDescriptor annotations in API classes services = [ "com.example.myext.api.MyService" ] requiresExtensions = [ // other extension IDs if you depend on them ] shadedPackages = [ // from : to (relocations applied to impl JAR) "com.example.dep" : "com.example.myext.shaded.dep" ] // Permissions scanPermissions = true // default: scan impl JAR + classpath requiredPermissions = [ // optional overrides/additions // "NETWORK", "FILE_READ", "FILE_WRITE", "THREADS", "EXEC", "NATIVE", // "REFLECTION", "CLASSLOADER", "SYSTEM_PROPS", "THREAD_INFO", "MEMORY_INFO", "JFR_EVENTS" ] } ``` Outputs - API JAR: `build/libs/--api.jar` with manifest entries: - `BTrace-Extension-Id`, `BTrace-Extension-Services`, `BTrace-Extension-Permissions`, `BTrace-Extension-Requires`, `BTrace-Shaded-Packages`, `BTrace-Extension-Impl`. - Impl JAR: `build/libs/--impl.jar` (shadowed, minimized) containing impl classes and shaded deps. - Distribution ZIP: `build/distributions/--extension.zip` bundling both JARs. Tasks - `buildApiJar`: builds the API JAR and writes extension metadata into the manifest. - `shadowJar`: builds the impl JAR from `impl` source set, applying relocations and minimization. - `packageExtension`: bundles API + Impl into a ZIP. Tips - Keep API small and stable; only types used by BTrace scripts or injected services belong in API. - Put all runtime dependencies into `impl` and shade them to avoid leaking into the target JVM. - If scanning is too conservative, add `requiredPermissions` explicitly. Package-level metadata (optional) - You may add `@ExtensionDescriptor` to `package-info.java` in your API package to document name/version/description. - The plugin relies on manifest attributes as the canonical metadata; `@ExtensionDescriptor` is for documentation only. Local Publish For Testing ``` ./gradlew :btrace-gradle-plugin:publishToMavenLocal ``` Then in your extension project, add `mavenLocal()` to repositories or pluginManagement and use the same plugin version. // Lints apiCtorSeverity = 'error' // 'off' | 'warn' | 'error' (default: 'error'). Warn/fail on public API classes with public constructors. ================================================ FILE: btrace-gradle-plugin/build.gradle ================================================ plugins { id 'groovy' id 'java-gradle-plugin' id 'maven-publish' } java { toolchain { languageVersion = JavaLanguageVersion.of(11) } } group = project.findProperty('GROUP') ?: (rootProject.group ?: 'org.openjdk.btrace') version = rootProject.version repositories { gradlePluginPortal() mavenCentral() } dependencies { implementation gradleApi() implementation localGroovy() // Match versions commonly cached in this repository to avoid extra fetches implementation 'org.ow2.asm:asm:9.9.1' implementation 'org.ow2.asm:asm-tree:9.9.1' } gradlePlugin { plugins { btraceExtension { id = 'org.openjdk.btrace.extension' implementationClass = 'org.openjdk.btrace.gradle.BTraceExtensionPlugin' displayName = 'BTrace Extension Plugin' description = 'Builds and packages BTrace extensions, scanning permissions and wiring manifests.' } } } publishing { publications { // java-gradle-plugin will add pluginMaven and marker publications automatically. // This block allows publishing to local/remote Maven repositories. withType(MavenPublication) { pom { name = 'BTrace Gradle Extension Plugin' description = 'Gradle plugin for building BTrace extensions' url = 'https://github.com/btraceio/btrace' licenses { license { name = 'GPL-2.0 with Classpath Exception' url = 'https://openjdk.org/legal/gplv2+ce.html' } } developers { developer { id = 'btrace' name = 'BTrace Project' url = 'https://github.com/btraceio' } } scm { url = 'https://github.com/btraceio/btrace' connection = 'scm:git:https://github.com/btraceio/btrace.git' developerConnection = 'scm:git:ssh://git@github.com:btraceio/btrace.git' } } } } } ================================================ FILE: btrace-gradle-plugin/src/main/groovy/org/openjdk/btrace/gradle/BTraceExtensionPlugin.groovy ================================================ package org.openjdk.btrace.gradle import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.GradleException import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.bundling.Zip import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.Copy import org.gradle.api.file.DuplicatesStrategy import java.lang.reflect.Modifier import java.net.URLClassLoader import org.objectweb.asm.ClassReader import org.objectweb.asm.Type import org.objectweb.asm.tree.AnnotationNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.TypeAnnotationNode import org.objectweb.asm.TypeReference import org.objectweb.asm.signature.SignatureReader import org.objectweb.asm.signature.SignatureVisitor import org.objectweb.asm.Opcodes class BTraceExtensionPlugin implements Plugin { @Override void apply(Project project) { // Apply required plugins project.plugins.apply('java') // Optionally apply maven-publish when available so we can publish artifacts easily try { project.plugins.apply('maven-publish') } catch (Throwable ignore) {} // Try to ensure Shadow is available; if resolution is blocked, we will emit a clear // error later with guidance. This is best-effort and safe when already applied. try { if (!project.pluginManager.hasPlugin('com.github.johnrengelman.shadow')) { // Respect opt-out def ext = project.extensions.findByType(BTraceExtensionMetadata) boolean shouldAutoApply = (ext == null) ? true : (ext.autoApplyShadow != false) if (shouldAutoApply) { project.logger.lifecycle("[BTRACE-EXT] Applying Shadow plugin automatically (com.github.johnrengelman.shadow) for ${project.path}") project.pluginManager.apply('com.github.johnrengelman.shadow') } else { project.logger.lifecycle("[BTRACE-EXT] Shadow auto-apply disabled (btraceExtension.autoApplyShadow=false) for ${project.path}") } } } catch (Throwable t) { project.logger.warn("[BTRACE-EXT] Unable to auto-apply Shadow plugin: ${t.message}. Apply it explicitly via plugins { id 'com.github.johnrengelman.shadow' } or alias(libs.plugins.shadow), or set btraceExtension.autoApplyShadow=true.") } // Create extension for metadata def extension = project.extensions.create('btraceExtension', BTraceExtensionMetadata) extension.version = project.version // Configure source sets project.sourceSets { api { java.srcDir 'src/api/java' resources.srcDir 'src/api/resources' } impl { java.srcDir 'src/impl/java' resources.srcDir 'src/impl/resources' compileClasspath += api.output runtimeClasspath += api.output } } // Configure dependency configurations project.configurations { apiImplementation.extendsFrom implementation apiCompileOnly.extendsFrom compileOnly implImplementation.extendsFrom implementation implImplementation.extendsFrom apiImplementation implCompileOnly.extendsFrom compileOnly } // Configure duplicate handling for resource tasks project.tasks.withType(Copy).configureEach { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } // Task: Build API JAR def buildApiJar = project.tasks.register('buildApiJar', Jar) { from project.sourceSets.api.output archiveClassifier = 'api' archiveBaseName = project.name } // Task: Validate API service interfaces (practical subset of rules) def validateServiceApis = project.tasks.register('validateServiceApis') { dependsOn project.tasks.named('compileApiJava') doLast { // Lint: API classes with public constructors (discouraged; use service-provided builders) def apiCtorIssues = [] as List project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (!f.name.endsWith('.class')) return def rel = dir.toPath().relativize(f.toPath()).toString() def internalName = rel.replace(File.separatorChar, (char)'/').replaceAll(/\.class$/, '') if (internalName.endsWith('package-info') || internalName.endsWith('module-info')) return FileInputStream is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode() cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) int acc = cn.access boolean isInterface = (acc & Opcodes.ACC_INTERFACE) != 0 boolean isAnnotation = (acc & Opcodes.ACC_ANNOTATION) != 0 boolean isPublic = (acc & Opcodes.ACC_PUBLIC) != 0 if (isPublic && !isInterface && !isAnnotation) { boolean hasPublicCtor = false (cn.methods ?: []).each { MethodNode mn -> if ("".equals(mn.name) && (mn.access & Opcodes.ACC_PUBLIC) != 0) { hasPublicCtor = true } } if (hasPublicCtor) { apiCtorIssues << internalName.replace('/', '.') } } } } if (!apiCtorIssues.isEmpty()) { def sev = (project.extensions.findByType(BTraceExtensionMetadata)?.apiCtorSeverity ?: 'warn') def msg = "[BTRACE-EXT] API classes with public constructors (avoid 'new' in probes; provide service builders/factories instead): ${apiCtorIssues}" if ("error".equalsIgnoreCase(sev)) { throw new GradleException(msg) } else if (!"off".equalsIgnoreCase(sev)) { project.logger.warn(msg) } } // Enforce: at most one package-level @ExtensionDescriptor across the project def extDescDesc = 'Lorg/openjdk/btrace/core/extensions/ExtensionDescriptor;' int extDescCount = 0 def scanForPkgDescriptors = { File classesDir -> if (classesDir == null || !classesDir.exists()) return classesDir.eachFileRecurse { f -> if (!f.name.endsWith('.class')) return if (!f.name.equals('package-info.class')) return FileInputStream is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode() cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) boolean has = false (cn.visibleAnnotations ?: []).each { has |= (it.desc == extDescDesc) } (cn.invisibleAnnotations ?: []).each { has |= (it.desc == extDescDesc) } if (has) extDescCount++ } } project.sourceSets.api.output.classesDirs.files.each { scanForPkgDescriptors(it) } try { project.sourceSets.impl.output.classesDirs.files.each { scanForPkgDescriptors(it) } } catch (Throwable ignore) {} if (extDescCount > 1) { throw new GradleException("[BTRACE-EXT] Found ${extDescCount} package-level @ExtensionDescriptor annotations. Only one is allowed per extension project (place it in API package-info.java).") } // Prefer explicitly configured services; otherwise, detect via @ServiceDescriptor annotations def services = extension.services as List if (!services || services.isEmpty()) { def detected = new LinkedHashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (!f.name.endsWith('.class')) return def rel = dir.toPath().relativize(f.toPath()).toString() def fq = rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '') FileInputStream is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode() cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) def isInterface = (cn.access & Opcodes.ACC_INTERFACE) != 0 def hasMarker = false (cn.visibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } (cn.invisibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } if (hasMarker && isInterface) detected.add(fq) } } services = detected as List if (!services.isEmpty()) { project.logger.lifecycle("[BTRACE-EXT] detected services via @ServiceDescriptor: ${services}") } else { project.logger.warn('[BTRACE-EXT] No services declared or detected; skipping validation') return } } def cp = new LinkedHashSet() cp.addAll(project.sourceSets.api.output.classesDirs.files) try { cp.addAll(project.sourceSets.api.compileClasspath.files) } catch (Throwable ignore) {} URLClassLoader cl = new URLClassLoader(cp.collect { it.toURI().toURL() } as URL[], (ClassLoader) null) def errors = [] def warnings = [] def perService = new LinkedHashMap>() def perServiceWarn = new LinkedHashMap>() // Build set of impl class names for purity checks def implClassNames = new HashSet() project.sourceSets.impl.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (f.name.endsWith('.class')) { def rel = dir.toPath().relativize(f.toPath()).toString() implClassNames.add(rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '')) } } } // Build set of api class names to avoid false positives when impl and api share packages/types def apiClassNames = new HashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (f.name.endsWith('.class')) { def rel = dir.toPath().relativize(f.toPath()).toString() apiClassNames.add(rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '')) } } } // Nullability annotation descriptors (configurable) def toDesc = { String fqcn -> 'L' + fqcn.replace('.', '/') + ';' } def defaultNullable = ['javax.annotation.Nullable','org.jspecify.annotations.Nullable','org.jetbrains.annotations.Nullable','jakarta.annotation.Nullable'] def defaultNonnull = ['javax.annotation.Nonnull','org.jspecify.annotations.NonNull','org.jetbrains.annotations.NotNull','jakarta.annotation.Nonnull'] def NULLABLE = ((extension.nullableAnnotations ?: defaultNullable).collect(toDesc)) as Set def NONNULL = ((extension.nonnullAnnotations ?: defaultNonnull).collect(toDesc)) as Set int countW020 = 0 int countW021 = 0 int count022 = 0 services.each { String fqcn -> try { Class c = Class.forName(fqcn, false, cl) def errs = perService.computeIfAbsent(fqcn) { [] } if (!c.isInterface()) { def msg = "Service '${fqcn}' must be a public top-level interface; found ${c.modifiers.toString()}\n Fix: Convert to a public top-level interface." errors << msg; errs << msg } if (!Modifier.isPublic(c.getModifiers())) { def msg = "Service '${fqcn}' must be public\n Fix: Add 'public' modifier." errors << msg; errs << msg } if (c.getDeclaringClass() != null) { def msg = "Service '${fqcn}' must be top-level (not inner)\n Fix: Move to a top-level interface." errors << msg; errs << msg } // No fields except constants (public static final primitives/Strings) c.getDeclaredFields().each { f -> int m = f.modifiers if (!(Modifier.isPublic(m) && Modifier.isStatic(m) && Modifier.isFinal(m))) { def msg = "Field '${fqcn}.${f.name}' not allowed; only compile-time constants permitted\n Fix: Remove mutable/stateful fields from interfaces." errors << msg; errs << msg } } // Prepare ASM method annotation info for this class def resName = fqcn.replace('.', '/') + '.class' def is = cl.getResourceAsStream(resName) if (is == null) { def msg = "Declared service '${fqcn}' not found in API output; ensure it is compiled in 'api' source set\n Fix: Add interface to src/api/java and to btraceExtension.services." errors << msg; perService.computeIfAbsent(fqcn) { [] } << msg return } byte[] classBytes try { classBytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(classBytes) def cn = new ClassNode() cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) def methodAnnoInfo = [:] // key=name+desc -> [retNullable, retNonNull, paramAnno: idx->[nullable, nonnull]] def permAnnoSet = new HashSet() // Collect ServiceDescriptor.permissions def collectServicePerms = { AnnotationNode an -> if (an == null) return if (an.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') { def vals = an.values ?: [] for (int i = 0; i < vals.size(); i += 2) { if (vals[i] == 'permissions') { def arr = vals[i+1] as List arr?.each { ev -> if (ev instanceof List && ev.size() >= 2) { permAnnoSet.add(String.valueOf(ev[1])) } } } } } } (cn.visibleAnnotations ?: []).each(collectServicePerms) (cn.invisibleAnnotations ?: []).each(collectServicePerms) cn.methods.each { MethodNode mn -> def key = mn.name + mn.desc def info = [retNullable:false, retNonNull:false, paramAnno:[:]] (mn.visibleAnnotations ?: []).each { AnnotationNode an -> if (NULLABLE.contains(an.desc)) info.retNullable = true if (NONNULL.contains(an.desc)) info.retNonNull = true collectServicePerms(an) } (mn.invisibleAnnotations ?: []).each { AnnotationNode an -> if (NULLABLE.contains(an.desc)) info.retNullable = true if (NONNULL.contains(an.desc)) info.retNonNull = true collectServicePerms(an) } (mn.visibleTypeAnnotations ?: []).each { TypeAnnotationNode tan -> def tr = new TypeReference(tan.typeRef) if (tr.sort == TypeReference.METHOD_RETURN) { if (NULLABLE.contains(tan.desc)) info.retNullable = true if (NONNULL.contains(tan.desc)) info.retNonNull = true } else if (tr.sort == TypeReference.METHOD_FORMAL_PARAMETER) { int pidx = tr.formalParameterIndex def pa = info.paramAnno.computeIfAbsent(pidx) { [nullable:false, nonnull:false] } if (NULLABLE.contains(tan.desc)) pa.nullable = true if (NONNULL.contains(tan.desc)) pa.nonnull = true } } (mn.invisibleTypeAnnotations ?: []).each { TypeAnnotationNode tan -> def tr = new TypeReference(tan.typeRef) if (tr.sort == TypeReference.METHOD_RETURN) { if (NULLABLE.contains(tan.desc)) info.retNullable = true if (NONNULL.contains(tan.desc)) info.retNonNull = true } else if (tr.sort == TypeReference.METHOD_FORMAL_PARAMETER) { int pidx = tr.formalParameterIndex def pa = info.paramAnno.computeIfAbsent(pidx) { [nullable:false, nonnull:false] } if (NULLABLE.contains(tan.desc)) pa.nullable = true if (NONNULL.contains(tan.desc)) pa.nonnull = true } } // Parameter annotations (non type-use) int paramCount = (mn.visibleParameterAnnotations ?: new List[0]).length for (int i = 0; i < paramCount; i++) { def lst = mn.visibleParameterAnnotations[i] if (lst == null) continue def pa = info.paramAnno.computeIfAbsent(i) { [nullable:false, nonnull:false] } lst.each { AnnotationNode an -> if (NULLABLE.contains(an.desc)) pa.nullable = true if (NONNULL.contains(an.desc)) pa.nonnull = true } } paramCount = (mn.invisibleParameterAnnotations ?: new List[0]).length for (int i = 0; i < paramCount; i++) { def lst = mn.invisibleParameterAnnotations[i] if (lst == null) continue def pa = info.paramAnno.computeIfAbsent(i) { [nullable:false, nonnull:false] } lst.each { AnnotationNode an -> if (NULLABLE.contains(an.desc)) pa.nullable = true if (NONNULL.contains(an.desc)) pa.nonnull = true } } methodAnnoInfo[key] = info } // Permission alignment: if permissions annotated in API, ensure either scanning is on // or declared requiredPermissions include at least those. if (!permAnnoSet.isEmpty()) { if (!extension.scanPermissions) { def declared = (extension.requiredPermissions ?: [])*.toString()*.toUpperCase(java.util.Locale.ROOT) as Set def missing = permAnnoSet.findAll { !declared.contains(String.valueOf(it).toUpperCase(java.util.Locale.ROOT)) } if (!missing.isEmpty()) { errors << "Service '${fqcn}' requires permissions ${missing} not covered by plugin requiredPermissions; enable scanPermissions or add them." } } } // Methods: no default methods, no checked throws, no forbidden signature types c.getMethods().each { m -> // Only methods declared on this interface (avoid Object methods) if (m.declaringClass != c) return if (m.isDefault()) { def msg = "Default method '${fqcn}#${m.name}' not allowed (Java 8 shim-compat)\n Fix: Move behavior to implementation; keep API pure." errors << msg; errs << msg } // Checked exceptions m.exceptionTypes.each { ex -> if (!(RuntimeException.isAssignableFrom(ex) || Error.isAssignableFrom(ex))) { def msg = "Method '${fqcn}#${m.name}' declares checked exception: ${ex.name}\n Fix: Remove checked exceptions from API signatures." errors << msg; errs << msg } } // Forbidden signature packages def forbid = [ 'java.io.', 'java.net.', 'java.nio.channels.', 'java.lang.reflect.' ] def allTypes = [] allTypes << m.returnType allTypes.addAll(m.parameterTypes) allTypes.each { t -> String n = t.name if (forbid.any { n.startsWith(it) }) { def msg = "Method '${fqcn}#${m.name}' uses forbidden type in signature: ${n}\n Fix: Restrict signatures to JDK types and API interfaces." errors << msg; errs << msg } if (implClassNames.contains(n)) { def msg = "API surface leaks implementation type in signature: ${n}\n Fix: Do not expose implementation types from src/impl in API signatures." errors << msg; errs << msg } } // Nullability + shimability def md = Type.getMethodDescriptor(Type.getType(m.returnType), m.parameterTypes.collect { Type.getType(it) } as Type[]) def key = m.name + md def info = methodAnnoInfo.get(key) ?: [retNullable:false, retNonNull:false, paramAnno:[:]] // Return nullability: only relevant for reference types (not void/primitives) if (m.returnType != Void.TYPE && !m.returnType.isPrimitive()) { if (!(info.retNullable || info.retNonNull)) { def msgWarn = "Missing nullability on return of '${fqcn}#${m.name}'\n Fix: Annotate with @NonNull or @Nullable." warnings << msgWarn; perServiceWarn.computeIfAbsent(fqcn) { [] } << msgWarn countW020++ } // Shimability: if return is interface, require @Nullable unless configured otherwise if (m.returnType.isInterface() && !info.retNullable) { def msg22 = "Return type '${m.returnType.name}' of '${fqcn}#${m.name}' must be @Nullable for shim compatibility\n Fix: Mark interface returns @Nullable or provide a documented default." if ((extension.shimabilitySeverity ?: 'error').equalsIgnoreCase('warn')) { warnings << msg22; perServiceWarn.computeIfAbsent(fqcn) { [] } << msg22 } else { errors << msg22; errs << msg22 } count022++ } } // Parameter nullability: recommend but do not enforce by default m.parameterTypes.eachWithIndex { pt, idx -> if (!(pt.isPrimitive())) { def pinfo = info.paramAnno.get(idx) ?: [nullable:false, nonnull:false] if (!(pinfo.nullable || pinfo.nonnull)) { def msg21 = "Missing nullability on parameter ${idx} of '${fqcn}#${m.name}'\n Fix: Annotate each parameter with @NonNull or @Nullable." if ((extension.nullabilitySeverity ?: 'warn').equalsIgnoreCase('error')) { def emsg = "Missing nullability on parameter ${idx} of '${fqcn}#${m.name}'\n Fix: Annotate each parameter with @NonNull or @Nullable." errors << emsg; errs << emsg } else if (!'off'.equalsIgnoreCase(extension.nullabilitySeverity ?: 'warn')) { warnings << msg21; perServiceWarn.computeIfAbsent(fqcn) { [] } << msg21 } countW021++ } } } } // Note: we intentionally avoid scanning raw class bytes for impl internal // names to prevent false positives (e.g., substring matches in class names). // Purity is enforced via explicit signature/generic parsing above. } catch (ClassNotFoundException cnf) { def msg = "Declared service '${fqcn}' not found in API output; ensure it is compiled in 'api' source set\n Fix: Add interface to src/api/java and to btraceExtension.services." errors << msg; perService.computeIfAbsent(fqcn) { [] } << msg } catch (Throwable t) { def msg = "Failed to analyze service '${fqcn}': ${t.message}" errors << msg; perService.computeIfAbsent(fqcn) { [] } << msg } } // Only warn when there is no way to obtain permissions: both scanning disabled // and no explicit requiredPermissions provided. If scanning is enabled (default), // permissions will be generated later when building the API JAR. if (!extension.scanPermissions && (!extension.requiredPermissions || extension.requiredPermissions.isEmpty())) { project.logger.warn("[BTRACE-EXT] No permissions source configured; enable scanPermissions or set requiredPermissions") } // Write grouped report def reportDir = new File(project.buildDir, 'reports/btrace') reportDir.mkdirs() def report = new File(reportDir, 'service-api-validation.txt') report.withWriter('UTF-8') { w -> w.println("BTrace Service API Validation Report") w.println("Project: ${project.path}") w.println() perService.each { k, v -> w.println("Service: ${k}") if (v.isEmpty()) { w.println(" OK") } else { v.each { w.println(" - ${it}") } } w.println() } if (!perServiceWarn.isEmpty()) { w.println("Warnings:") perServiceWarn.each { k, v -> if (v.isEmpty()) return w.println("Service: ${k}") v.each { w.println(" - ${it}") } w.println() } } // Summary w.println("Summary:") w.println(" Missing @Nullable on interface returns (shimability): ${count022}") if (!'off'.equalsIgnoreCase(extension.nullabilitySeverity ?: 'warn')) { w.println(" Missing return nullability annotations: ${countW020}") w.println(" Missing parameter nullability annotations: ${countW021}") } w.println() w.println("Total errors: ${errors.size()}") } project.logger.lifecycle("[BTRACE-EXT] validation report: ${project.relativePath(report)}") if (!errors.isEmpty()) { errors.each { project.logger.error("[BTRACE-EXT] ${it}") } throw new GradleException("BTrace extension API validation failed with ${errors.size()} error(s)") } if (!warnings.isEmpty()) { warnings.each { project.logger.warn("[BTRACE-EXT] ${it}") } } project.logger.lifecycle("[BTRACE-EXT] validateServiceApis: OK for ${services.size()} service(s)") } } // Summary report task that never fails; emits grouped violations with fixes def serviceApiValidationReport = project.tasks.register('serviceApiValidationReport') { dependsOn project.tasks.named('compileApiJava') doLast { // Reuse the validation task to produce the report; do not fail build try { project.tasks.named('validateServiceApis').get().actions.each { it.execute(project.tasks.named('validateServiceApis').get()) } } catch (Throwable t) { // ignore failures; report was already written by validate task } def report = new File(project.buildDir, 'reports/btrace/service-api-validation.txt') if (report.exists()) { project.logger.lifecycle("[BTRACE-EXT] validation report available at: ${project.relativePath(report)}") project.logger.quiet(report.getText('UTF-8')) } else { project.logger.lifecycle('[BTRACE-EXT] No report generated (no services or validation skipped)') } } } // Task: Build Implementation JAR (requires Shadow plugin applied by the consumer project) def implJarProviderRef = new Object[1] project.pluginManager.withPlugin('com.github.johnrengelman.shadow') { def shadowJarProvider = project.tasks.named('shadowJar', Jar) project.afterEvaluate { shadowJarProvider.configure { from project.sourceSets.impl.output configurations = [project.configurations.implRuntimeClasspath] archiveClassifier = 'impl' archiveBaseName = project.name // Apply relocations from extension metadata extension.shadedPackages.each { from, to -> relocate from, to } // Minimize to remove unused classes minimize() } } implJarProviderRef[0] = shadowJarProvider } // Wrapper task to build implementation jar via selected strategy def buildImplJar = project.tasks.register('buildImplJar') { doFirst { if (implJarProviderRef[0] == null) { throw new IllegalStateException("Shadow plugin ('com.github.johnrengelman.shadow') must be applied in the project using the BTrace extension plugin.") } } dependsOn { implJarProviderRef[0] } } // Configure API JAR manifest with extension metadata project.afterEvaluate { if (implJarProviderRef[0] == null) { def ext = project.extensions.findByType(BTraceExtensionMetadata) boolean autoApplied = (ext == null) ? true : (ext.autoApplyShadow != false) String hint = autoApplied ? "Ensure the Shadow plugin is resolvable/available." : "Enable auto-apply (btraceExtension.autoApplyShadow=true) or apply Shadow explicitly." throw new GradleException("[BTRACE-EXT] Shadow plugin is required for ${project.path}. Apply id 'com.github.johnrengelman.shadow' (or alias(libs.plugins.shadow)). ${hint}") } def implArchiveProvider = ((org.gradle.api.tasks.TaskProvider) implJarProviderRef[0]).flatMap { it.archiveFile } buildApiJar.configure { // Ensure we consider impl jar and classpath as inputs and we run when they change dependsOn { implJarProviderRef[0] } inputs.files(implArchiveProvider) inputs.files(project.configurations.implRuntimeClasspath) doFirst { // Compute permissions: scan impl jar and transitive classpath, then merge with overrides def implJar = implArchiveProvider.get().asFile Set scannedPerms = [] as Set if (extension.scanPermissions) { Set cp = [] as Set try { cp = project.configurations.implRuntimeClasspath.resolve() as Set } catch (Throwable ignore) { } scannedPerms = PermissionScanner.scan(implJar, cp) } Set mergedPerms = [] as Set mergedPerms.addAll(scannedPerms) mergedPerms.addAll(extension.requiredPermissions) // Emit concise info about permissions for traceability if (extension.scanPermissions) { project.logger.lifecycle("[BTRACE-EXT] permissions: scanned=${scannedPerms} merged=${mergedPerms}") } else { project.logger.lifecycle("[BTRACE-EXT] permissions: merged=${mergedPerms} (scan disabled)") } // Cross-check: ensure manifest permissions cover descriptor-declared requirements try { def cp = new LinkedHashSet() cp.addAll(project.sourceSets.api.output.classesDirs.files) try { cp.addAll(project.sourceSets.api.compileClasspath.files) } catch (Throwable ignore) {} URLClassLoader cl = new URLClassLoader(cp.collect { it.toURI().toURL() } as URL[], (ClassLoader) null) def enumConstName = { Object enumConst -> if (enumConst instanceof List && enumConst.size() >= 2) { return String.valueOf(enumConst[1]).toUpperCase(Locale.ROOT) } return null } Set annotatedPerms = [] as Set (extension.services as List).each { String fqcn -> try { def res = fqcn.replace('.', '/') + '.class' def is = cl.getResourceAsStream(res) if (is == null) return def cr = new ClassReader(is) def cn = new ClassNode() cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) (cn.visibleAnnotations ?: []).each { an -> if (an.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') { def vals = an.values ?: [] for (int i = 0; i < vals.size(); i += 2) { if (vals[i] == 'permissions') { def arr = vals[i+1] as List arr?.each { ev -> def n = enumConstName(ev) if (n != null) annotatedPerms.add(n) } } } } } (cn.invisibleAnnotations ?: []).each { an -> if (an.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') { def vals = an.values ?: [] for (int i = 0; i < vals.size(); i += 2) { if (vals[i] == 'permissions') { def arr = vals[i+1] as List arr?.each { ev -> def n = enumConstName(ev) if (n != null) annotatedPerms.add(n) } } } } } } catch (Throwable ignore) { } } // Also include package-level ExtensionDescriptor.permissions() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (!f.name.equals('package-info.class')) return FileInputStream is2 = new FileInputStream(f) byte[] bytes2 try { bytes2 = is2.bytes } finally { try { is2.close() } catch (Throwable ignore) {} } def cr2 = new ClassReader(bytes2) def cn2 = new ClassNode(); cr2.accept(cn2, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) (cn2.visibleAnnotations ?: []).each { an -> if (an.desc == 'Lorg/openjdk/btrace/core/extensions/ExtensionDescriptor;') { def vals = an.values ?: [] for (int i = 0; i < vals.size(); i += 2) { if (vals[i] == 'permissions') { def arr = vals[i+1] as List arr?.each { ev -> def n = enumConstName(ev) if (n != null) annotatedPerms.add(n) } } } } } (cn2.invisibleAnnotations ?: []).each { an -> if (an.desc == 'Lorg/openjdk/btrace/core/extensions/ExtensionDescriptor;') { def vals = an.values ?: [] for (int i = 0; i < vals.size(); i += 2) { if (vals[i] == 'permissions') { def arr = vals[i+1] as List arr?.each { ev -> def n = enumConstName(ev) if (n != null) annotatedPerms.add(n) } } } } } } } Set manifestPerms = mergedPerms.collect { it.toString().toUpperCase(Locale.ROOT) } as Set Set missing = annotatedPerms.findAll { !manifestPerms.contains(it) } as Set if (!missing.isEmpty()) { throw new GradleException("[BTRACE-EXT] Manifest permissions missing annotated requirements: ${missing}. Fix: enable scanPermissions or add to requiredPermissions.") } } catch (GradleException ge) { throw ge } catch (Throwable t) { project.logger.warn("[BTRACE-EXT] permission alignment check failed to run: ${t.message}") } def shadedPkgs = extension.shadedPackages.collect { k, v -> "$k->$v" }.join(',') def servicesStr = extension.services.join(',') def requiresStr = extension.requiresExtensions.join(',') def permsStr = mergedPerms.join(',') manifest { attributes( 'BTrace-Extension-Id': extension.id, 'BTrace-Extension-Version': extension.version, 'BTrace-Extension-Name': extension.name, 'BTrace-Extension-Description': extension.description, 'BTrace-API-Version': '2.3+', 'BTrace-Java-Version': '8+', 'BTrace-Extension-Services': { if (servicesStr && !servicesStr.isEmpty()) return servicesStr // If not explicitly configured, detect services via @ServiceDescriptor in API output def detected = new LinkedHashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (!f.name.endsWith('.class')) return def rel = dir.toPath().relativize(f.toPath()).toString() def fq = rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '') FileInputStream is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) boolean isInterface = (cn.access & Opcodes.ACC_INTERFACE) != 0 boolean hasMarker = false (cn.visibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } (cn.invisibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } if (hasMarker && isInterface) detected.add(fq) } } return detected.join(',') }.call(), 'BTrace-Extension-Requires': requiresStr, 'BTrace-Shaded-Packages': shadedPkgs, 'BTrace-Extension-Impl': "${project.name}-${extension.version}-impl.jar", 'BTrace-Extension-Permissions': permsStr, // Read exports index (if present) and add a short summary attribute 'BTrace-Extension-Exports': { try { def idxFile = new File(project.buildDir, 'generated/resources/btraceExports/META-INF/btrace/exports.index') List lines = [] if (idxFile.exists()) { lines = idxFile.readLines('UTF-8') } else { lines = extension.services as List } int limit = 20 def head = lines.take(limit) def extra = Math.max(0, lines.size() - limit) return head.join(',') + (extra > 0 ? ", +${extra} more" : '') } catch (Throwable ignore) { return servicesStr } }.call() ) } } } } // Generate shims Java sources for API interfaces def shimsSrcDir = new File(project.buildDir, 'generated/sources/btraceShims/api') def shimsClsDir = new File(project.buildDir, 'generated/classes/btraceShims/api') def shimsResDir = new File(project.buildDir, 'generated/resources/btraceShims') def generateServiceShims = project.tasks.register('generateServiceShims') { dependsOn project.tasks.named('compileApiJava') doLast { shimsSrcDir.mkdirs() // Build ClassLoader over API outputs to reflect methods def cp = new LinkedHashSet() cp.addAll(project.sourceSets.api.output.classesDirs.files) try { cp.addAll(project.sourceSets.api.compileClasspath.files) } catch (Throwable ignore) {} URLClassLoader cl = new URLClassLoader(cp.collect { it.toURI().toURL() } as URL[], (ClassLoader) null) // Discover ALL API interfaces in the api output def apiInterfaces = new LinkedHashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (f.name.endsWith('.class')) { def rel = dir.toPath().relativize(f.toPath()).toString() def fq = rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '') // Use ASM to filter interfaces (skip annotations) def is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) int acc = cr.access boolean isInterface = (acc & Opcodes.ACC_INTERFACE) != 0 boolean isAnnotation = (acc & Opcodes.ACC_ANNOTATION) != 0 if (isInterface && !isAnnotation) { apiInterfaces.add(fq) } } } } if (apiInterfaces.isEmpty()) { project.logger.warn('[BTRACE-EXT] No API interfaces found; skipping shim generation') return } // Optionally restrict to interfaces reachable from services via descriptors + generics def targetInterfaces = new LinkedHashSet() if (extension.generateShimsReachableOnly) { def services = (extension.services as List) ?: [] // BFS closure over referenced API interface types def visited = new HashSet() def queue = new ArrayDeque(services) while (!queue.isEmpty()) { def cur = queue.removeFirst() if (cur == null || visited.contains(cur)) continue visited.add(cur) if (apiInterfaces.contains(cur)) targetInterfaces.add(cur) try { def res = cur.replace('.', '/') + '.class' def is = cl.getResourceAsStream(res) if (is == null) continue byte[] bytes; try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) def addType = { String fq -> if (fq!=null && apiInterfaces.contains(fq) && !visited.contains(fq)) queue.addLast(fq) } // class generics if (cn.signature != null) { try { new SignatureReader(cn.signature).accept(new SignatureVisitor(Opcodes.ASM9){ void visitClassType(String name){ addType(name?.replace('/', '.')) } void visitInnerClassType(String name){ addType(name?.replace('/', '.')) } SignatureVisitor visitSuperclass(){ return this } SignatureVisitor visitInterface(){ return this } SignatureVisitor visitTypeArgument(char w){ return this } SignatureVisitor visitClassBound(){ return this } SignatureVisitor visitInterfaceBound(){ return this } }) } catch(Throwable ignore){} } // method descriptors + generics cn.methods?.each { MethodNode mn -> def mt = Type.getMethodType(mn.desc) [mt.returnType].each { t -> if (t.sort==Type.OBJECT) addType(t.className) } mt.argumentTypes?.each { t -> if (t.sort==Type.OBJECT) addType(t.className) } if (mn.signature != null) { try { new SignatureReader(mn.signature).accept(new SignatureVisitor(Opcodes.ASM9){ void visitClassType(String name){ addType(name?.replace('/', '.')) } void visitInnerClassType(String name){ addType(name?.replace('/', '.')) } SignatureVisitor visitParameterType(){ return this } SignatureVisitor visitReturnType(){ return this } SignatureVisitor visitExceptionType(){ return this } SignatureVisitor visitArrayType(){ return this } SignatureVisitor visitTypeArgument(char w){ return this } }) } catch(Throwable ignore){} } } } catch (Throwable ignore) {} } } else { targetInterfaces.addAll(apiInterfaces) } // Generate shims for selected API interfaces targetInterfaces.each { String fqcn -> try { Class c = Class.forName(fqcn, false, cl) if (!c.isInterface()) return def pkg = c.package?.name ?: '' def simple = c.simpleName def shimPkg = (pkg ? pkg + '.btrace.shim' : 'btrace.shim') def dir = new File(shimsSrcDir, shimPkg.replace('.', '/')) dir.mkdirs() def noopName = "NoOp${simple}" def throwName = "Throwing${simple}" def methods = c.getMethods().findAll { it.declaringClass == c } // Emit NoOp new File(dir, noopName + '.java').withWriter('UTF-8') { w -> w.println("package ${shimPkg};") w.println() w.println("public final class ${noopName} implements ${fqcn} {") w.println(" public static final ${fqcn} INSTANCE = new ${noopName}();") methods.each { m -> def ret = m.returnType def params = [] m.parameterTypes.eachWithIndex { pt, idx -> params << (pt.name + " p" + idx) } w.println(" public ${ret.name} ${m.name}(${params.join(', ')}) {") if (ret == Void.TYPE) { w.println(" return;") } else if (ret.isPrimitive()) { if (ret == Boolean.TYPE) w.println(" return false;") else if (ret == Character.TYPE) w.println(" return (char)0;") else if (ret == Byte.TYPE) w.println(" return (byte)0;") else if (ret == Short.TYPE) w.println(" return (short)0;") else if (ret == Integer.TYPE) w.println(" return 0;") else if (ret == Long.TYPE) w.println(" return 0L;") else if (ret == Float.TYPE) w.println(" return 0f;") else if (ret == Double.TYPE) w.println(" return 0d;") } else if (ret.isArray() && ret.componentType.isInterface() && apiInterfaces.contains(ret.componentType.name)) { // Return empty array of API interface type w.println(" return new ${ret.componentType.name}[0];") } else if (ret.isInterface() && apiInterfaces.contains(ret.name)) { // Return nested shim INSTANCE (or this if fluent self-return) if (ret.name == fqcn) { w.println(" return this;") } else { def retSimple = ret.simpleName def retShimFqcn = (ret.package?.name ? ret.package.name + '.btrace.shim.NoOp' + retSimple : 'btrace.shim.NoOp' + retSimple) w.println(" return ${retShimFqcn}.INSTANCE;") } } else if (m.genericReturnType instanceof java.lang.reflect.ParameterizedType) { def pt = (java.lang.reflect.ParameterizedType) m.genericReturnType def raw = (pt.rawType instanceof Class) ? (Class) pt.rawType : null if (raw != null) { def rawName = raw.name if (rawName == 'java.util.List' || rawName == 'java.util.Collection' || rawName == 'java.lang.Iterable') { w.println(" return java.util.Collections.emptyList();") } else if (rawName == 'java.util.Set') { w.println(" return java.util.Collections.emptySet();") } else if (rawName == 'java.util.Queue' || rawName == 'java.util.Deque') { w.println(" return new java.util.ArrayDeque<>();") } else if (rawName == 'java.util.Map') { w.println(" return java.util.Collections.emptyMap();") } else if (rawName == 'java.util.Optional') { w.println(" return java.util.Optional.empty();") } else if (rawName == 'java.util.stream.Stream') { w.println(" return java.util.stream.Stream.empty();") } else if (rawName == 'java.util.stream.IntStream') { w.println(" return java.util.stream.IntStream.empty();") } else if (rawName == 'java.util.stream.LongStream') { w.println(" return java.util.stream.LongStream.empty();") } else if (rawName == 'java.util.stream.DoubleStream') { w.println(" return java.util.stream.DoubleStream.empty();") } else { w.println(" return null;") } } } else { w.println(" return null;") } w.println(" }") } // equals/hashCode/toString w.println(" public boolean equals(Object o) { return this == o; }") w.println(" public int hashCode() { return System.identityHashCode(this); }") w.println(" public String toString() { return \"NoOp${simple}\"; }") w.println("}") } // Emit Throwing new File(dir, throwName + '.java').withWriter('UTF-8') { w -> w.println("package ${shimPkg};") w.println() w.println("public final class ${throwName} implements ${fqcn} {") w.println(" public static final ${fqcn} INSTANCE = new ${throwName}();") w.println(" private static RuntimeException unavailable() { return new IllegalStateException(\"BTrace optional service unavailable: ${fqcn}\"); }") methods.each { m -> def ret = m.returnType def params = [] m.parameterTypes.eachWithIndex { pt, idx -> params << (pt.name + " p" + idx) } w.println(" public ${ret.name} ${m.name}(${params.join(', ')}) {") w.println(" throw unavailable();") w.println(" }") } // equals/hashCode/toString w.println(" public boolean equals(Object o) { return this == o; }") w.println(" public int hashCode() { return System.identityHashCode(this); }") w.println(" public String toString() { return \"Throwing${simple}\"; }") w.println("}") } } catch (Throwable t) { project.logger.warn("[BTRACE-EXT] Skipping shim generation for ${fqcn}: ${t.message}") } } } } def compileServiceShims = project.tasks.register('compileServiceShims', JavaCompile) { t -> dependsOn generateServiceShims source = project.fileTree(shimsSrcDir) destinationDirectory.set(shimsClsDir) classpath = project.files(project.sourceSets.api.output.classesDirs, project.sourceSets.api.compileClasspath) options.release = 8 } def generateShimIndex = project.tasks.register('generateShimIndex') { dependsOn generateServiceShims doLast { // Recompute the same target set as generateServiceShims (respect generateShimsReachableOnly) def apiInterfaces = new LinkedHashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (f.name.endsWith('.class')) { def rel = dir.toPath().relativize(f.toPath()).toString() def fq = rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '') def is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) int acc = cr.access boolean isInterface = (acc & Opcodes.ACC_INTERFACE) != 0 boolean isAnnotation = (acc & Opcodes.ACC_ANNOTATION) != 0 if (isInterface && !isAnnotation) apiInterfaces.add(fq) } } } if (apiInterfaces.isEmpty()) return def cp = new LinkedHashSet() cp.addAll(project.sourceSets.api.output.classesDirs.files) try { cp.addAll(project.sourceSets.api.compileClasspath.files) } catch (Throwable ignore) {} URLClassLoader cl = new URLClassLoader(cp.collect { it.toURI().toURL() } as URL[], (ClassLoader) null) def targetInterfaces = new LinkedHashSet() if (extension.generateShimsReachableOnly) { def services = (extension.services as List) ?: [] if (services.isEmpty()) { // fall back to detected annotated services project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (!f.name.endsWith('.class')) return def rel = dir.toPath().relativize(f.toPath()).toString() def fq = rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '') FileInputStream is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) boolean isInterface = (cn.access & Opcodes.ACC_INTERFACE) != 0 boolean hasMarker = false (cn.visibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } (cn.invisibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } if (hasMarker && isInterface) services.add(fq) } } } def visited = new HashSet() def queue = new ArrayDeque(services) while (!queue.isEmpty()) { def cur = queue.removeFirst() if (cur == null || visited.contains(cur)) continue visited.add(cur) if (apiInterfaces.contains(cur)) targetInterfaces.add(cur) try { def res = cur.replace('.', '/') + '.class' def is = cl.getResourceAsStream(res) if (is == null) continue byte[] bytes; try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) def addType = { String fq -> if (fq!=null && apiInterfaces.contains(fq) && !visited.contains(fq)) queue.addLast(fq) } if (cn.signature != null) { try { new SignatureReader(cn.signature).accept(new SignatureVisitor(Opcodes.ASM9){ void visitClassType(String name){ addType(name?.replace('/', '.')) } void visitInnerClassType(String name){ addType(name?.replace('/', '.')) } SignatureVisitor visitSuperclass(){ return this } SignatureVisitor visitInterface(){ return this } SignatureVisitor visitTypeArgument(char w){ return this } SignatureVisitor visitClassBound(){ return this } SignatureVisitor visitInterfaceBound(){ return this } }) } catch(Throwable ignore){} } cn.methods?.each { MethodNode mn -> def mt = Type.getMethodType(mn.desc) [mt.returnType].each { t -> if (t.sort==Type.OBJECT) addType(t.className) } mt.argumentTypes?.each { t -> if (t.sort==Type.OBJECT) addType(t.className) } if (mn.signature != null) { try { new SignatureReader(mn.signature).accept(new SignatureVisitor(Opcodes.ASM9){ void visitClassType(String name){ addType(name?.replace('/', '.')) } void visitInnerClassType(String name){ addType(name?.replace('/', '.')) } SignatureVisitor visitParameterType(){ return this } SignatureVisitor visitReturnType(){ return this } SignatureVisitor visitExceptionType(){ return this } SignatureVisitor visitArrayType(){ return this } SignatureVisitor visitTypeArgument(char w){ return this } }) } catch(Throwable ignore){} } } } catch (Throwable ignore) {} } } else { targetInterfaces.addAll(apiInterfaces) } def idxFile = new File(shimsResDir, 'META-INF/btrace/shims.index') idxFile.parentFile.mkdirs() idxFile.withWriter('UTF-8') { w -> targetInterfaces.each { String fqcn -> def pkg = fqcn.contains('.') ? fqcn.substring(0, fqcn.lastIndexOf('.')) : '' def simple = fqcn.substring(fqcn.lastIndexOf('.')+1) def shimPkg = (pkg ? pkg + '.btrace.shim' : 'btrace.shim') def noop = shimPkg + '.NoOp' + simple def thrw = shimPkg + '.Throwing' + simple w.println("${fqcn} = ${noop}, ${thrw}") } } } } // Exports index: collect all API types referenced in service method signatures (return and params) def exportsResDir = new File(project.buildDir, 'generated/resources/btraceExports') def generateExportsIndex = project.tasks.register('generateExportsIndex') { dependsOn project.tasks.named('compileApiJava') doLast { def services = extension.services as List if (!services || services.isEmpty()) { def detected = new LinkedHashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (!f.name.endsWith('.class')) return def rel = dir.toPath().relativize(f.toPath()).toString() def fq = rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '') FileInputStream is = new FileInputStream(f) byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) boolean isInterface = (cn.access & Opcodes.ACC_INTERFACE) != 0 boolean hasMarker = false (cn.visibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } (cn.invisibleAnnotations ?: []).each { hasMarker |= (it.desc == 'Lorg/openjdk/btrace/core/extensions/ServiceDescriptor;') } if (hasMarker && isInterface) detected.add(fq) } } services = detected as List } if (!services || services.isEmpty()) return def cp = new LinkedHashSet() cp.addAll(project.sourceSets.api.output.classesDirs.files) try { cp.addAll(project.sourceSets.api.compileClasspath.files) } catch (Throwable ignore) {} URLClassLoader cl = new URLClassLoader(cp.collect { it.toURI().toURL() } as URL[], (ClassLoader) null) // Collect known API/impl class names to validate placement def apiClassNames = new HashSet() project.sourceSets.api.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (f.name.endsWith('.class')) { def rel = dir.toPath().relativize(f.toPath()).toString() apiClassNames.add(rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '')) } } } def implClassNames = new HashSet() project.sourceSets.impl.output.classesDirs.files.each { File dir -> if (!dir.exists()) return dir.eachFileRecurse { f -> if (f.name.endsWith('.class')) { def rel = dir.toPath().relativize(f.toPath()).toString() implClassNames.add(rel.replace(File.separatorChar, (char)'.').replaceAll(/\.class$/, '')) } } } def exports = new LinkedHashSet() services.each { String fqcn -> try { exports.add(fqcn) def res = fqcn.replace('.', '/') + '.class' def is = cl.getResourceAsStream(res) if (is == null) return byte[] bytes try { bytes = is.bytes } finally { try { is.close() } catch (Throwable ignore) {} } def cr = new ClassReader(bytes) def cn = new ClassNode() cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG) // Class-level generic signature: capture bounds/supertypes/interface types if (cn.signature != null) { try { def collector = new SignatureVisitor(Opcodes.ASM9) { @Override void visitClassType(String name) { addType(name?.replace('/', '.')) } @Override void visitInnerClassType(String name) { addType(name?.replace('/', '.')) } @Override SignatureVisitor visitSuperclass() { return this } @Override SignatureVisitor visitInterface() { return this } @Override SignatureVisitor visitTypeArgument(char wildcard) { return this } @Override SignatureVisitor visitClassBound() { return this } @Override SignatureVisitor visitInterfaceBound() { return this } } new SignatureReader(cn.signature).accept(collector) } catch (Throwable ignored) { } } // Helper to add a referenced type found in signatures def addType = { String fq -> if (fq == null) return if (fq.startsWith('java.') || fq.startsWith('javax.') || fq.startsWith('jakarta.')) return if (apiClassNames.contains(fq)) { exports.add(fq) } else if (implClassNames.contains(fq)) { throw new GradleException("[BTRACE-EXT] API method references implementation type '${fq}'. Fix: move type to API module so probes can use it.") } } cn.methods?.each { MethodNode mn -> def mt = Type.getMethodType(mn.desc) def all = [] as List all << mt.returnType all.addAll(mt.argumentTypes.toList()) all.each { Type t -> def fq = null if (t.getSort() == Type.OBJECT) { fq = t.getClassName() } else if (t.getSort() == Type.ARRAY && t.getElementType().getSort() == Type.OBJECT) { fq = t.getElementType().getClassName() } addType(fq) } // Parse generic signature to capture type arguments if (mn.signature != null) { try { def collector = new SignatureVisitor(Opcodes.ASM9) { @Override SignatureVisitor visitParameterType() { return this } @Override SignatureVisitor visitReturnType() { return this } @Override SignatureVisitor visitExceptionType() { return this } @Override SignatureVisitor visitArrayType() { return this } @Override void visitClassType(String name) { addType(name?.replace('/', '.')) } @Override void visitInnerClassType(String name) { addType(name?.replace('/', '.')) } @Override void visitTypeVariable(String name) { } @Override void visitBaseType(char descriptor) { } @Override SignatureVisitor visitTypeArgument(char wildcard) { return this } } new SignatureReader(mn.signature).accept(collector) } catch (Throwable ignored) { } } } } catch (GradleException ge) { throw ge } catch (Throwable t) { project.logger.warn("[BTRACE-EXT] export scan skipped for ${fqcn}: ${t.message}") } } if (!exports.isEmpty()) { def idxFile = new File(exportsResDir, 'META-INF/btrace/exports.index') idxFile.parentFile.mkdirs() idxFile.withWriter('UTF-8') { w -> exports.each { w.println(it) } } project.logger.lifecycle("[BTRACE-EXT] exports: ${exports.size()} types") } } } def packageExtension = project.tasks.register('packageExtension', Zip) { dependsOn buildApiJar, buildImplJar, compileServiceShims, generateShimIndex, generateExportsIndex, validateServiceApis archiveBaseName = project.name archiveClassifier = 'extension' from(buildApiJar.map { it.archiveFile }) from(((org.gradle.api.tasks.TaskProvider) implJarProviderRef[0]).flatMap { it.archiveFile }) } // Disable default jar task (use packageExtension instead) project.tasks.named('jar') { enabled = false } // Configure artifacts for consumption project.configurations { apiElements.outgoing.artifacts.clear() apiElements.outgoing.artifact(buildApiJar) runtimeElements.outgoing.artifacts.clear() runtimeElements.outgoing.artifact(buildApiJar) runtimeElements.outgoing.artifact(packageExtension) } // Make extension ZIP the default archive artifact project.artifacts { archives packageExtension } // Include shims classes and index in API JAR buildApiJar.configure { dependsOn compileServiceShims, generateShimIndex, generateExportsIndex from(shimsClsDir) from(shimsResDir) from(exportsResDir) } // Aux tasks: API sources and javadoc jars def apiSourcesJar = project.tasks.register('apiSourcesJar', Jar) { archiveBaseName = project.name archiveClassifier = 'api-sources' duplicatesStrategy = DuplicatesStrategy.EXCLUDE from project.sourceSets.api.allSource } def apiJavadoc = project.tasks.register('apiJavadoc', Javadoc) { source = project.sourceSets.api.allJava classpath = project.files(project.sourceSets.api.output.classesDirs, project.sourceSets.api.compileClasspath) destinationDir = new File(project.buildDir, 'docs/api') options.encoding = 'UTF-8' } def apiJavadocJar = project.tasks.register('apiJavadocJar', Jar) { dependsOn apiJavadoc archiveBaseName = project.name archiveClassifier = 'api-javadoc' from apiJavadoc.map { it.destinationDir } } // Configure publishing if maven-publish is present if (project.plugins.hasPlugin('maven-publish')) { project.publishing { publications { mavenExtension(org.gradle.api.publish.maven.MavenPublication) { groupId = project.group as String artifactId = project.name version = project.version as String artifact(buildApiJar) { classifier = 'api' } artifact(apiSourcesJar) artifact(apiJavadocJar) artifact(packageExtension) { classifier = 'extension' } pom { name = project.provider { extension.name } description = project.provider { extension.description } } } } } } // Tests can see both api and impl outputs project.afterEvaluate { project.dependencies { testImplementation project.sourceSets.api.output testImplementation project.sourceSets.impl.output } // Fail build on validation errors via check lifecycle project.tasks.matching { it.name == 'check' }.configureEach { it.dependsOn validateServiceApis } } } } class BTraceExtensionMetadata { String id String version String name String description List services = [] List requiresExtensions = [] Map shadedPackages = [:] // Whether to auto-apply the Shadow plugin. Default: true boolean autoApplyShadow = true boolean scanPermissions = true List requiredPermissions = [] // Configurable nullability annotations (FQCN). Defaults cover javax/jspecify/jetbrains/jakarta List nullableAnnotations = [] List nonnullAnnotations = [] // Validation severity knobs // nullabilitySeverity: 'off' | 'warn' | 'error' (default: 'warn') String nullabilitySeverity = 'warn' // shimabilitySeverity: 'warn' | 'error' (default: 'error') String shimabilitySeverity = 'error' // apiCtorSeverity: 'off' | 'warn' | 'error' (default: 'error') String apiCtorSeverity = 'error' // Generate shims only for interfaces reachable from declared services (via signatures + generics) // Set to false to generate shims for all API interfaces. boolean generateShimsReachableOnly = true } ================================================ FILE: btrace-gradle-plugin/src/main/groovy/org/openjdk/btrace/gradle/PermissionScanner.groovy ================================================ package org.openjdk.btrace.gradle import java.io.File import java.io.FileInputStream import java.io.IOException import java.io.InputStream import java.util.ArrayDeque import java.util.Collection import java.util.Collections import java.util.Deque import java.util.List import java.util.Set import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipInputStream import org.objectweb.asm.ClassReader import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.InvokeDynamicInsnNode import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.LdcInsnNode /** * Scans a compiled extension implementation JAR to infer required permissions * based on referenced JDK/runtime APIs. */ final class PermissionScanner { static Set scan(File jarFile, Collection classpath = Collections.emptyList()) { Set perms = [] as Set if (jarFile == null || !jarFile.exists()) return perms List sources = [] sources.add(jarFile) classpath?.each { File f -> if (f == null || !f.exists()) return def path = f.absolutePath if (path.contains('/btrace-core') || path.contains('/btrace-runtime') || path.contains('/btrace-instr') || path.contains('/btrace-agent')) return sources.add(f) } Set visited = [] as Set Deque queue = new ArrayDeque<>() new ZipFile(jarFile).withCloseable { zip -> zip.entries().each { e -> if (!e.isDirectory() && e.name.endsWith('.class')) { queue.add(e.name.substring(0, e.name.length() - '.class'.length())) } } } while (!queue.isEmpty()) { String cn = queue.removeFirst() if (!visited.add(cn)) continue InputStream is = openClassStream(cn, sources) if (is == null) continue try { ClassReader cr = new ClassReader(is) ClassNode node = new ClassNode() cr.accept(node, 0) node.methods.each { m -> for (AbstractInsnNode insn = m.instructions.first; insn != null; insn = insn.next) { if (insn instanceof MethodInsnNode) { MethodInsnNode min = (MethodInsnNode) insn handleMethod(min, perms, m, insn) String owner = min.owner if (owner != null && !owner.startsWith('java/') && !owner.startsWith('javax/')) { queue.add(owner) } } else if (insn instanceof FieldInsnNode) { handleField(((FieldInsnNode) insn), perms) } } } } finally { is.close() } } return perms } private static void scanClass(InputStream in, Set perms) { ClassReader cr = new ClassReader(in) ClassNode cn = new ClassNode() cr.accept(cn, 0) cn.methods.each { m -> for (AbstractInsnNode insn = m.instructions.first; insn != null; insn = insn.next) { if (insn instanceof MethodInsnNode) { handleMethod(((MethodInsnNode) insn), perms, m, insn) } else if (insn instanceof FieldInsnNode) { handleField(((FieldInsnNode) insn), perms) } else if (insn instanceof InvokeDynamicInsnNode) { // no-op for now } } } } private static void handleMethod(MethodInsnNode min, Set perms, def methodNode, AbstractInsnNode current) { String owner = min.owner String name = min.name if (owner.startsWith('java/net/') || owner.startsWith('javax/net/')) { perms.add('NETWORK') } if (owner.startsWith('java/io/') || owner.startsWith('java/nio/file/')) { if (owner == 'java/io/FileOutputStream' || owner == 'java/io/FileWriter' || owner == 'java/io/PrintWriter') { perms.add('FILE_WRITE'); perms.add('FILE_READ') } else if (owner == 'java/io/RandomAccessFile') { String mode = findPreviousLdcString(methodNode, current, 8) if ('r'.equals(mode)) { perms.add('FILE_READ') } else { perms.add('FILE_WRITE'); perms.add('FILE_READ') } } else if (owner.startsWith('java/nio/file/Files')) { if (name.startsWith('newOutputStream') || name.startsWith('write') || name.startsWith('create') || name.startsWith('delete')) { perms.add('FILE_WRITE') } else if (name.startsWith('newInputStream') || name.startsWith('read')) { perms.add('FILE_READ') } } else if (owner == 'java/io/FileInputStream' || owner == 'java/io/BufferedInputStream' || owner == 'java/io/InputStreamReader') { perms.add('FILE_READ') } } if (owner == 'java/io/File') { if (name in ['delete', 'deleteOnExit', 'renameTo', 'mkdir', 'mkdirs', 'createNewFile', 'setReadable', 'setWritable', 'setExecutable']) { perms.add('FILE_WRITE') } } if (owner == 'java/nio/file/Files') { if (name in ['createFile','createDirectory','createDirectories','delete','deleteIfExists','move','setAttribute','setPosixFilePermissions','copy','write']) { perms.add('FILE_WRITE') } } if ((owner == 'java/nio/channels/FileChannel' || owner == 'java/nio/channels/AsynchronousFileChannel') && name == 'open') { if (hasOpenOptionWrite(methodNode, current, 20)) { perms.add('FILE_WRITE') } else { perms.add('FILE_READ') } } if (owner == 'java/util/logging/FileHandler' && name == '') { perms.add('FILE_WRITE') } if (owner == 'java/lang/Thread' || owner.startsWith('java/util/concurrent/')) { perms.add('THREADS') } if (owner == 'sun/misc/Unsafe' || owner == 'jdk/internal/misc/Unsafe') { perms.add('NATIVE') } if (owner == 'java/lang/System' && (name == 'load' || name == 'loadLibrary')) { perms.add('NATIVE') } if (owner == 'java/lang/Runtime' && name == 'exec') { perms.add('EXEC') } if (owner == 'java/lang/ProcessBuilder' && name == '') { perms.add('EXEC') } if (owner.startsWith('java/lang/reflect/')) { perms.add('REFLECTION') } if (owner == 'java/lang/ClassLoader' || owner == 'java/lang/Class' && name.startsWith('forName')) { perms.add('CLASSLOADER') } if (owner == 'java/lang/System' && name == 'getProperty') { perms.add('SYSTEM_PROPS') } if (owner == 'java/lang/management/ManagementFactory') { perms.add('THREAD_INFO'); perms.add('MEMORY_INFO') } if (owner.startsWith('com/sun/management/')) { perms.add('MEMORY_INFO') } if (owner.startsWith('jdk/jfr/') || owner.startsWith('org/openjdk/btrace/core/jfr/')) { perms.add('JFR_EVENTS') } } private static void handleField(FieldInsnNode fin, Set perms) { String owner = fin.owner String name = fin.name if (owner == 'java/lang/System' && name == 'props') { perms.add('SYSTEM_PROPS') } } private static String findPreviousLdcString(def methodNode, AbstractInsnNode from, int window) { if (methodNode == null || from == null) return null int count = 0 for (AbstractInsnNode p = from.previous; p != null && count < window; p = p.previous, count++) { if (p.opcode == Opcodes.LDC && p instanceof LdcInsnNode) { def cst = ((LdcInsnNode) p).cst if (cst instanceof String) return (String) cst } } return null } private static boolean hasOpenOptionWrite(def methodNode, AbstractInsnNode from, int window) { if (methodNode == null || from == null) return true int count = 0 for (AbstractInsnNode p = from.previous; p != null && count < window; p = p.previous, count++) { if (p instanceof FieldInsnNode) { FieldInsnNode fin = (FieldInsnNode) p if (fin.owner == 'java/nio/file/StandardOpenOption') { if (['WRITE','APPEND','CREATE','CREATE_NEW','TRUNCATE_EXISTING','DELETE_ON_CLOSE'].contains(fin.name)) { return true } } } } return false } private static InputStream openClassStream(String internalName, List sources) { String entry = internalName.endsWith('.class') ? internalName : (internalName + '.class') for (File f : sources) { if (f.isDirectory()) { File cf = new File(f, entry) if (cf.exists()) return new FileInputStream(cf) } else if (f.name.endsWith('.jar')) { try { def zip = new ZipFile(f) def ze = zip.getEntry(entry) if (ze != null) { return zip.getInputStream(ze) } else { zip.close() } } catch (IOException ignored) { } } } return null } } ================================================ FILE: btrace-instr/build.gradle ================================================ import java.nio.file.Files import java.nio.file.Paths plugins { id 'java' } dependencies { implementation libs.slf4j implementation libs.asm implementation libs.asm.tree implementation libs.asm.util implementation libs.autoService implementation libs.jctools implementation project(':btrace-core') implementation project(':btrace-extension') implementation project(':btrace-runtime') implementation project(':btrace-compiler') testImplementation libs.asm.util testImplementation libs.slf4j.simple testImplementation libs.junit.jupiter testImplementation project(':btrace-client') testImplementation project(':btrace-extensions:btrace-statsd') testImplementation project(':btrace-extensions:btrace-utils') } compileJava { javaCompiler = javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(8)) } } compileTestJava { options.fork = true options.forkOptions.executable = "${getJavac(8)}" } task compileTestProbes { dependsOn compileTestJava, processTestResources doLast { def path = project(':btrace-instr').sourceSets.main.runtimeClasspath def loader = new URLClassLoader(path.collect { f -> f.toURL() } as URL[]) def compiler = loader.loadClass('org.openjdk.btrace.compiler.Compiler') // Use test runtime classpath so test-only extension APIs are available def rtCp = sourceSets.test.runtimeClasspath def extraCp = files( buildDir.toPath().resolve("classes/java/test"), buildDir.toPath().resolve("classes/java/java11_dummy"), buildDir.toPath().resolve("resources/test") ) // Include extension API outputs explicitly to avoid jar task coupling def utilsApi = project(':btrace-extensions:btrace-utils').sourceSets.api.output def statsdApi = project(':btrace-extensions:btrace-statsd').sourceSets.api.output def fullCp = rtCp.plus(extraCp).plus(utilsApi).plus(statsdApi) def cpPath = fullCp.getAsPath() def args = [ "-cp", cpPath, "-d", buildDir.toPath().resolve("classes") ] def files = fileTree(dir: "src/test/btrace", include: '**/*.java', exclude: 'verifier/**/*.java').findAll { it != null }.collect { it } args.addAll(files) // Help verifier discover extension services by exposing classpath to System property scanning def oldCp = System.getProperty('java.class.path') try { System.setProperty('btrace.allow.undeclared.services', 'true') System.setProperty('java.class.path', cpPath) compiler.main(args as String[]) } finally { if (oldCp != null) System.setProperty('java.class.path', oldCp) System.clearProperty('btrace.allow.undeclared.services') } } } test { dependsOn cleanTest inputs.files compileTestProbes.outputs testLogging.showStandardStreams = true def props = new Properties() props.load(Files.newInputStream(Paths.get(System.getenv("JAVA_HOME"), "release"))) if (!props.getProperty("JAVA_VERSION")?.contains("1.8")) { jvmArgs '-XX:+IgnoreUnrecognizedVMOptions', '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-opens', 'java.base/jdk.internal.reflect=ALL-UNNAMED', '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.reflect=ALL-UNNAMED' } if (project.hasProperty("updateTestData")) { jvmArgs '-Dupdate.test.data=true' } jvmArgs "-Dtest.resources=${projectDir}/src/test/resources" jvmArgs "-Dproject.version=${project.version}" } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAccessInstrumentor.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; /** * This visitor helps in inserting code whenever an array access is done. Code to insert on array * access may be decided by derived class. By default, this class inserts code to print array * access. * * @author A. Sundararajan */ public class ArrayAccessInstrumentor extends MethodInstrumentor { public ArrayAccessInstrumentor( ClassLoader cl, MethodVisitor mv, MethodInstrumentorHelper mHelper, String parentClz, String superClz, int access, String name, String desc) { super(cl, mv, mHelper, parentClz, superClz, access, name, desc); } @Override public void visitInsn(int opcode) { boolean arrayload = false; boolean arraystore = false; switch (opcode) { case IALOAD: case LALOAD: case FALOAD: case DALOAD: case AALOAD: case BALOAD: case CALOAD: case SALOAD: arrayload = true; break; case IASTORE: case LASTORE: case FASTORE: case DASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: arraystore = true; break; } if (arrayload) { onBeforeArrayLoad(opcode); } else if (arraystore) { onBeforeArrayStore(opcode); } super.visitInsn(opcode); if (arrayload) { onAfterArrayLoad(opcode); } else if (arraystore) { onAfterArrayStore(opcode); } } protected final boolean locationTypeMismatch(Location loc, Type arrtype, Type itemType) { return !loc.getType().isEmpty() && (!loc.getType().equals(arrtype.getClassName()) && !loc.getType().equals(itemType.getClassName())); } protected void onBeforeArrayLoad(int opcode) {} protected void onAfterArrayLoad(int opcode) {} protected void onBeforeArrayStore(int opcode) {} protected void onAfterArrayStore(int opcode) {} } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAllocInstrumentor.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import static org.objectweb.asm.Opcodes.ANEWARRAY; import static org.objectweb.asm.Opcodes.NEWARRAY; import org.objectweb.asm.MethodVisitor; /** * This visitor helps in inserting code whenever an array is allocated. The code to insert on method * entry may be decided by derived class. By default, this class inserts code to print allocated * array objects. * * @author A. Sundararajan */ public class ArrayAllocInstrumentor extends MethodInstrumentor { public ArrayAllocInstrumentor( ClassLoader cl, MethodVisitor mv, MethodInstrumentorHelper mHelper, String parentClz, String superClz, int access, String name, String desc) { super(cl, mv, mHelper, parentClz, superClz, access, name, desc); } @Override public void visitIntInsn(int opcode, int operand) { String desc = null; if (opcode == NEWARRAY) { desc = InstrumentUtils.arrayDescriptorFor(operand); onBeforeArrayNew(getPlainType(desc), 1); } super.visitIntInsn(opcode, operand); if (opcode == NEWARRAY) { onAfterArrayNew(getPlainType(desc), 1); } } @Override public void visitTypeInsn(int opcode, String desc) { if (opcode == ANEWARRAY) { onBeforeArrayNew("L" + desc + ";", 1); } super.visitTypeInsn(opcode, desc); if (opcode == ANEWARRAY) { onAfterArrayNew("L" + desc + ";", 1); } } @Override public void visitMultiANewArrayInsn(String desc, int dims) { String type = getPlainType(desc); onBeforeArrayNew(type, dims); super.visitMultiANewArrayInsn(desc, dims); onAfterArrayNew(type, dims); } protected void onBeforeArrayNew(String desc, int dims) { asm.println("before allocating " + desc); } protected void onAfterArrayNew(String desc, int dims) { asm.dup().printObject(); } private String getPlainType(String desc) { int index = desc.lastIndexOf('[') + 1; if (index > 0) { return desc.substring(index); } return desc; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/Assembler.java ================================================ /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.openjdk.btrace.runtime.Interval; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.AASTORE; import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.ANEWARRAY; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.BALOAD; import static org.objectweb.asm.Opcodes.BASTORE; import static org.objectweb.asm.Opcodes.BIPUSH; import static org.objectweb.asm.Opcodes.CALOAD; import static org.objectweb.asm.Opcodes.CASTORE; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DALOAD; import static org.objectweb.asm.Opcodes.DASTORE; import static org.objectweb.asm.Opcodes.DCONST_0; import static org.objectweb.asm.Opcodes.DRETURN; import static org.objectweb.asm.Opcodes.DSUB; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.DUP2; import static org.objectweb.asm.Opcodes.DUP2_X1; import static org.objectweb.asm.Opcodes.DUP2_X2; import static org.objectweb.asm.Opcodes.DUP_X1; import static org.objectweb.asm.Opcodes.DUP_X2; import static org.objectweb.asm.Opcodes.FALOAD; import static org.objectweb.asm.Opcodes.FASTORE; import static org.objectweb.asm.Opcodes.FCONST_0; import static org.objectweb.asm.Opcodes.FRETURN; import static org.objectweb.asm.Opcodes.FSUB; import static org.objectweb.asm.Opcodes.GETFIELD; import static org.objectweb.asm.Opcodes.GETSTATIC; import static org.objectweb.asm.Opcodes.GOTO; import static org.objectweb.asm.Opcodes.IALOAD; import static org.objectweb.asm.Opcodes.IASTORE; import static org.objectweb.asm.Opcodes.ICONST_0; import static org.objectweb.asm.Opcodes.ICONST_1; import static org.objectweb.asm.Opcodes.ICONST_2; import static org.objectweb.asm.Opcodes.ICONST_3; import static org.objectweb.asm.Opcodes.ICONST_4; import static org.objectweb.asm.Opcodes.ICONST_5; import static org.objectweb.asm.Opcodes.ICONST_M1; import static org.objectweb.asm.Opcodes.IFNE; import static org.objectweb.asm.Opcodes.IF_ICMPGT; import static org.objectweb.asm.Opcodes.IF_ICMPLT; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.IRETURN; import static org.objectweb.asm.Opcodes.ISTORE; import static org.objectweb.asm.Opcodes.ISUB; import static org.objectweb.asm.Opcodes.LALOAD; import static org.objectweb.asm.Opcodes.LASTORE; import static org.objectweb.asm.Opcodes.LCONST_0; import static org.objectweb.asm.Opcodes.LCONST_1; import static org.objectweb.asm.Opcodes.LRETURN; import static org.objectweb.asm.Opcodes.LSUB; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.POP; import static org.objectweb.asm.Opcodes.PUTFIELD; import static org.objectweb.asm.Opcodes.PUTSTATIC; import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.SALOAD; import static org.objectweb.asm.Opcodes.SASTORE; import static org.objectweb.asm.Opcodes.SIPUSH; import static org.objectweb.asm.Opcodes.SWAP; import static org.openjdk.btrace.instr.Constants.BOOLEAN_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.BOOLEAN_VALUE; import static org.openjdk.btrace.instr.Constants.BOOLEAN_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.BOX_BOOLEAN_DESC; import static org.openjdk.btrace.instr.Constants.BOX_BYTE_DESC; import static org.openjdk.btrace.instr.Constants.BOX_CHARACTER_DESC; import static org.openjdk.btrace.instr.Constants.BOX_DOUBLE_DESC; import static org.openjdk.btrace.instr.Constants.BOX_FLOAT_DESC; import static org.openjdk.btrace.instr.Constants.BOX_INTEGER_DESC; import static org.openjdk.btrace.instr.Constants.BOX_LONG_DESC; import static org.openjdk.btrace.instr.Constants.BOX_SHORT_DESC; import static org.openjdk.btrace.instr.Constants.BOX_VALUEOF; import static org.openjdk.btrace.instr.Constants.BTRACE_LEVEL_FLD; import static org.openjdk.btrace.instr.Constants.BYTE_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.BYTE_VALUE; import static org.openjdk.btrace.instr.Constants.BYTE_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.CHARACTER_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.CHAR_VALUE; import static org.openjdk.btrace.instr.Constants.CHAR_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.DOUBLE_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.DOUBLE_VALUE; import static org.openjdk.btrace.instr.Constants.DOUBLE_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.FLOAT_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.FLOAT_VALUE; import static org.openjdk.btrace.instr.Constants.FLOAT_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.INTEGER_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.INT_DESC; import static org.openjdk.btrace.instr.Constants.INT_VALUE; import static org.openjdk.btrace.instr.Constants.INT_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.LONG_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.LONG_VALUE; import static org.openjdk.btrace.instr.Constants.LONG_VALUE_DESC; import static org.openjdk.btrace.instr.Constants.NUMBER_INTERNAL; import static org.openjdk.btrace.instr.Constants.SHORT_BOXED_INTERNAL; import static org.openjdk.btrace.instr.Constants.SHORT_VALUE; import static org.openjdk.btrace.instr.Constants.SHORT_VALUE_DESC; /** * Convenient fluent wrapper over the ASM method visitor * * @author Jaroslav Bachorik */ @SuppressWarnings("UnusedReturnValue") public final class Assembler { private final MethodVisitor mv; private final MethodInstrumentorHelper mHelper; public Assembler(MethodVisitor mv, MethodInstrumentorHelper mHelper) { this.mv = mv; this.mHelper = mHelper; } public Assembler push(int value) { if (value >= -1 && value <= 5) { mv.visitInsn(ICONST_0 + value); } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, value); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, value); } else { mv.visitLdcInsn(value); } return this; } public Assembler arrayLoad(Type type) { mv.visitInsn(type.getOpcode(IALOAD)); return this; } public Assembler arrayStore(Type type) { mv.visitInsn(type.getOpcode(IASTORE)); return this; } public Assembler jump(int opcode, Label l) { mv.visitJumpInsn(opcode, l); return this; } public Assembler ldc(Object o) { if (o == null) { return loadNull(); } if (o instanceof Integer) { int i = (int) o; if (i >= -1 && i <= 5) { int opcode = -1; switch (i) { case 0: { opcode = ICONST_0; break; } case 1: { opcode = ICONST_1; break; } case 2: { opcode = ICONST_2; break; } case 3: { opcode = ICONST_3; break; } case 4: { opcode = ICONST_4; break; } case 5: { opcode = ICONST_5; break; } case -1: { opcode = ICONST_M1; break; } } mv.visitInsn(opcode); return this; } } if (o instanceof Long) { long l = (long) o; if (l >= 0 && l <= 1) { int opcode = -1; switch ((int) l) { case 0: { opcode = LCONST_0; break; } case 1: { opcode = LCONST_1; break; } } mv.visitInsn(opcode); return this; } } mv.visitLdcInsn(o); return this; } public Assembler sub(Type t) { int opcode = -1; switch (t.getSort()) { case Type.SHORT: case Type.BYTE: case Type.INT: { opcode = ISUB; break; } case Type.LONG: { opcode = LSUB; break; } case Type.FLOAT: { opcode = FSUB; break; } case Type.DOUBLE: { opcode = DSUB; break; } } if (opcode != -1) { mv.visitInsn(opcode); } return this; } public Assembler loadNull() { mv.visitInsn(ACONST_NULL); return this; } public Assembler loadLocal(Type type, int index) { mv.visitVarInsn(type.getOpcode(ILOAD), index); return this; } public Assembler storeLocal(Type type, int index) { mv.visitVarInsn(type.getOpcode(ISTORE), index); return this; } public Assembler storeField(Type owner, String name, Type t) { mv.visitFieldInsn(PUTFIELD, owner.getInternalName(), name, t.getDescriptor()); return this; } public Assembler storeStaticField(Type owner, String name, Type t) { mv.visitFieldInsn(PUTSTATIC, owner.getInternalName(), name, t.getDescriptor()); return this; } public Assembler loadField(Type owner, String name, Type t) { mv.visitFieldInsn(GETFIELD, owner.getInternalName(), name, t.getDescriptor()); return this; } public Assembler loadStaticField(Type owner, String name, Type t) { mv.visitFieldInsn(GETSTATIC, owner.getInternalName(), name, t.getDescriptor()); return this; } public Assembler pop() { mv.visitInsn(POP); return this; } public Assembler dup() { mv.visitInsn(DUP); return this; } public Assembler dup_x1() { mv.visitInsn(DUP_X1); return this; } public Assembler dup_x2() { mv.visitInsn(DUP_X2); return this; } public Assembler dup2() { mv.visitInsn(DUP2); return this; } public Assembler dup2_x1() { mv.visitInsn(DUP2_X1); return this; } public Assembler dup2_x2() { mv.visitInsn(DUP2_X2); return this; } public Assembler swap() { mv.visitInsn(SWAP); return this; } public Assembler newInstance(Type t) { mv.visitTypeInsn(NEW, t.getInternalName()); return this; } public Assembler newArray(Type t) { mv.visitTypeInsn(ANEWARRAY, t.getInternalName()); return this; } public Assembler dupArrayValue(int arrayOpcode) { switch (arrayOpcode) { case IALOAD: case FALOAD: case AALOAD: case BALOAD: case CALOAD: case SALOAD: case IASTORE: case FASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: dup(); break; case LALOAD: case DALOAD: case LASTORE: case DASTORE: dup2(); break; } return this; } public Assembler dupReturnValue(int returnOpcode) { switch (returnOpcode) { case IRETURN: case FRETURN: case ARETURN: mv.visitInsn(DUP); break; case LRETURN: case DRETURN: mv.visitInsn(DUP2); break; case RETURN: break; default: throw new IllegalArgumentException("not return"); } return this; } public Assembler dupValue(Type type) { switch (type.getSize()) { case 1: dup(); break; case 2: dup2(); break; } return this; } public Assembler dupValue(String desc) { int typeCode = desc.charAt(0); switch (typeCode) { case '[': case 'L': case 'Z': case 'C': case 'B': case 'S': case 'I': mv.visitInsn(DUP); break; case 'J': case 'D': mv.visitInsn(DUP2); break; default: throw new InstrumentationException( String.format("Invalid bytecode signature in dupValue: %s", desc)); } return this; } public Assembler box(Type type) { return box(type.getDescriptor()); } public Assembler box(String desc) { int typeCode = desc.charAt(0); switch (typeCode) { case '[': case 'L': break; case 'Z': invokeStatic(BOOLEAN_BOXED_INTERNAL, BOX_VALUEOF, BOX_BOOLEAN_DESC); break; case 'C': invokeStatic(CHARACTER_BOXED_INTERNAL, BOX_VALUEOF, BOX_CHARACTER_DESC); break; case 'B': invokeStatic(BYTE_BOXED_INTERNAL, BOX_VALUEOF, BOX_BYTE_DESC); break; case 'S': invokeStatic(SHORT_BOXED_INTERNAL, BOX_VALUEOF, BOX_SHORT_DESC); break; case 'I': invokeStatic(INTEGER_BOXED_INTERNAL, BOX_VALUEOF, BOX_INTEGER_DESC); break; case 'J': invokeStatic(LONG_BOXED_INTERNAL, BOX_VALUEOF, BOX_LONG_DESC); break; case 'F': invokeStatic(FLOAT_BOXED_INTERNAL, BOX_VALUEOF, BOX_FLOAT_DESC); break; case 'D': invokeStatic(DOUBLE_BOXED_INTERNAL, BOX_VALUEOF, BOX_DOUBLE_DESC); break; } return this; } public Assembler unbox(Type type) { return unbox(type.getDescriptor()); } public Assembler unbox(String desc) { int typeCode = desc.charAt(0); switch (typeCode) { case '[': case 'L': mv.visitTypeInsn(CHECKCAST, Type.getType(desc).getInternalName()); break; case 'Z': mv.visitTypeInsn(CHECKCAST, BOOLEAN_BOXED_INTERNAL); invokeVirtual(BOOLEAN_BOXED_INTERNAL, BOOLEAN_VALUE, BOOLEAN_VALUE_DESC); break; case 'C': mv.visitTypeInsn(CHECKCAST, CHARACTER_BOXED_INTERNAL); invokeVirtual(CHARACTER_BOXED_INTERNAL, CHAR_VALUE, CHAR_VALUE_DESC); break; case 'B': mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); invokeVirtual(NUMBER_INTERNAL, BYTE_VALUE, BYTE_VALUE_DESC); break; case 'S': mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); invokeVirtual(NUMBER_INTERNAL, SHORT_VALUE, SHORT_VALUE_DESC); break; case 'I': mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); invokeVirtual(NUMBER_INTERNAL, INT_VALUE, INT_VALUE_DESC); break; case 'J': mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); invokeVirtual(NUMBER_INTERNAL, LONG_VALUE, LONG_VALUE_DESC); break; case 'F': mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); invokeVirtual(NUMBER_INTERNAL, FLOAT_VALUE, FLOAT_VALUE_DESC); break; case 'D': mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); invokeVirtual(NUMBER_INTERNAL, DOUBLE_VALUE, DOUBLE_VALUE_DESC); break; } return this; } public Assembler defaultValue(String desc) { int typeCode = desc.charAt(0); switch (typeCode) { case '[': case 'L': mv.visitInsn(ACONST_NULL); break; case 'Z': case 'C': case 'B': case 'S': case 'I': mv.visitInsn(ICONST_0); break; case 'J': mv.visitInsn(LCONST_0); break; case 'F': mv.visitInsn(FCONST_0); break; case 'D': mv.visitInsn(DCONST_0); break; } return this; } public Assembler println(String msg) { mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn(msg); invokeVirtual("java/io/PrintStream", "println", "(Ljava/lang/String;)V"); return this; } // print the object on the top of the stack public Assembler printObject() { mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitInsn(SWAP); mv.visitMethodInsn( INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); return this; } public Assembler invokeVirtual(String owner, String method, String desc) { mv.visitMethodInsn(INVOKEVIRTUAL, owner, method, desc, false); return this; } public Assembler invokeSpecial(String owner, String method, String desc) { mv.visitMethodInsn(INVOKESPECIAL, owner, method, desc, false); return this; } public Assembler invokeStatic(String owner, String method, String desc) { mv.visitMethodInsn(INVOKESTATIC, owner, method, desc, false); return this; } public Assembler invokeDynamic( String name, String descriptor, Handle bootstrap, Object... bootstrapArguments) { mv.visitInvokeDynamicInsn(name, descriptor, bootstrap, bootstrapArguments); return this; } public Assembler invokeInterface(String owner, String method, String desc) { mv.visitMethodInsn(INVOKEVIRTUAL, owner, method, desc, true); return this; } public Assembler getStatic(String owner, String name, String desc) { mv.visitFieldInsn(GETSTATIC, owner, name, desc); return this; } public Assembler putStatic(String owner, String name, String desc) { mv.visitFieldInsn(PUTSTATIC, owner, name, desc); return this; } public Assembler label(Label l) { mv.visitLabel(l); return this; } public Assembler addLevelCheck(String clsName, Level level, Label jmp) { return addLevelCheck(clsName, level.getValue(), jmp); } public Assembler addLevelCheck(String clsName, Interval itv, Label jmp) { getStatic(clsName, "$btrace$$level", INT_DESC); if (itv.getA() <= 0) { if (itv.getB() != Integer.MAX_VALUE) { ldc(itv.getB()); jump(IF_ICMPGT, jmp); } } else if (itv.getA() < itv.getB()) { if (itv.getB() == Integer.MAX_VALUE) { ldc(itv.getA()); jump(IF_ICMPLT, jmp); } else { ldc(itv.getA()); jump(IF_ICMPLT, jmp); getStatic(clsName, "$btrace$$level", INT_DESC); ldc(itv.getB()); jump(IF_ICMPGT, jmp); } } return this; } /** * Compares the instrumentation level interval against the runtime value. * *

If the runtime value is fitting the level interval there will be 0 on stack upon return from * this method. Otherwise there will be -1. * * @param clsName The probe class name * @param level The probe instrumentation level * @return itself */ public Assembler compareLevel(String clsName, Level level) { Interval itv = level.getValue(); if (itv.getA() <= 0) { if (itv.getB() != Integer.MAX_VALUE) { ldc(itv.getB()); getStatic(clsName, BTRACE_LEVEL_FLD, INT_DESC); sub(Type.INT_TYPE); } } else if (itv.getA() < itv.getB()) { if (itv.getB() == Integer.MAX_VALUE) { getStatic(clsName, BTRACE_LEVEL_FLD, INT_DESC); ldc(itv.getA()); sub(Type.INT_TYPE); } else { Label l1 = new Label(); Label l2 = new Label(); ldc(itv.getA()); jump(IF_ICMPLT, l1); getStatic(clsName, BTRACE_LEVEL_FLD, INT_DESC); ldc(itv.getB()); jump(IF_ICMPGT, l1); ldc(0); Label l3 = new Label(); label(l3); mHelper.insertFrameSameStack(l3); jump(GOTO, l2); label(l1); mHelper.insertFrameSameStack(l1); ldc(-1); label(l2); mHelper.insertFrameSameStack(l2); } } return this; } public Label openLinkerCheck() { Label l = new Label(); invokeStatic(Constants.LINKING_FLAG_INTERNAL, "get", "()I"); // if the linking flag is 0, then we are not in a reentrant call jump(IFNE, l); return l; } public void closeLinkerCheck(Label l) { label(l); mHelper.insertFrameSameStack(l); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceBCPClassLoader.java ================================================ package org.openjdk.btrace.instr; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import org.openjdk.btrace.core.SharedSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class BTraceBCPClassLoader extends URLClassLoader { private static final Logger log = LoggerFactory.getLogger(BTraceBCPClassLoader.class); BTraceBCPClassLoader(SharedSettings settings) { super(getBCPUrls(settings), null); } private static URL[] getBCPUrls(SharedSettings settings) { String bcp = settings.getBootClassPath(); if (bcp != null && !bcp.isEmpty()) { List urls = new ArrayList<>(); for (String cpElement : bcp.split(File.pathSeparator)) { try { urls.add(new File(cpElement).toURI().toURL()); } catch (MalformedURLException e) { log.debug("Invalid classpath definition: {}", cpElement, e); } } return urls.toArray(new URL[0]); } return new URL[0]; } @Override public Class loadClass(String name) throws ClassNotFoundException { // delegate class loading to parent directly ClassLoader parent = getParent(); if (parent == null) { parent = ClassLoader.getSystemClassLoader(); } return parent.loadClass(name); } @Override public URL getResource(String name) { // follow the standard process to load resources return super.getResource(name); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassReader.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A hacked version of ClassReader * allowing fast access to class name, class version, super type, interfaces and annotations. * * @author Jaroslav Bachorik */ final class BTraceClassReader extends ClassReader { private static final Logger log = LoggerFactory.getLogger(BTraceClassReader.class); private static final Method getAttributesMthd; private static final Method readAnnotationValuesMthd; private static final Field itemsFld; private static final Field mslFld; static { Method m1 = null, m2 = null; Field f1 = null, f2 = null; try { m1 = ClassReader.class.getDeclaredMethod("getFirstAttributeOffset"); m1.setAccessible(true); m2 = ClassReader.class.getDeclaredMethod( "readElementValue", AnnotationVisitor.class, int.class, String.class, char[].class); m2.setAccessible(true); f1 = ClassReader.class.getDeclaredField("cpInfoOffsets"); f1.setAccessible(true); f2 = ClassReader.class.getDeclaredField("maxStringLength"); f2.setAccessible(true); } catch (Exception e) { log.debug("Failed to initialize BTraceClassReader reflection fields", e); } getAttributesMthd = m1; readAnnotationValuesMthd = m2; itemsFld = f1; mslFld = f2; } private final ClassLoader cl; BTraceClassReader(ClassLoader cl, byte[] bytes) { super(bytes); this.cl = cl; } BTraceClassReader(ClassLoader cl, InputStream in) throws IOException { super(in); this.cl = cl; } public static void bailout() { throw BailoutException.INSTANCE; } public ClassLoader getClassLoader() { return cl; } /** * The associated Java class name ('.' is the package delimiter) * * @return */ public String getJavaClassName() { return getClassName().replace('/', '.'); } public String[] readClassSupers() { String[] ifaces = getInterfaces(); String[] supers = Arrays.copyOf(ifaces, ifaces.length + 1); supers[supers.length - 1] = getSuperName(); return supers; } public boolean isInterface() { return (getAccess() & Opcodes.ACC_INTERFACE) != 0; } public boolean isBTrace() { return getAnnotationTypes().contains(Constants.BTRACE_DESC); } public Collection getAnnotationTypes() { Collection types = new HashSet<>(); char[] c = new char[getMaxStringLength()]; // buffer used to read strings int anns = getAnnotationsOffset(c); if (anns != -1) { for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { types.add(Type.getType(readUTF8(v, c)).getClassName()); v = skipAnnotationValues(v + 2, c); if (v == -1) break; } } return types; } public int getClassVersion() { try { if (itemsFld != null) { int[] items = (int[]) itemsFld.get(this); return readInt(items[1] - 7); } } catch (Exception e) { log.debug("Failed to read class major version", e); } return 51; // default to Java 7 } @Override public void accept(ClassVisitor cv, Attribute[] atrbts, int i) { try { super.accept(cv, atrbts, i); } catch (BailoutException e) { // expected; ignore } } @Override public void accept(ClassVisitor cv, int i) { try { super.accept(cv, i); } catch (BailoutException e) { // expected; ignore } } private int getAttributes() { try { if (getAttributesMthd != null) { return (int) getAttributesMthd.invoke(this); } } catch (Exception e) { log.debug("Failed to get first attribute offset", e); } return -1; } private int getAnnotationsOffset(char[] buf) { int u = getAttributes(); if (u == -1) { return -1; } for (int i = readUnsignedShort(u - 2); i > 0; --i) { String attrName = readUTF8(u, buf); int attributeLength = readInt(u + 2); u += 6; if ("RuntimeVisibleAnnotations".equals(attrName)) { return u; } u += attributeLength; } return -1; } private int skipAnnotationValues(int off, char[] buf) { try { if (readAnnotationValuesMthd != null) { return (int) readAnnotationValuesMthd.invoke(this, null, off, null, buf); } } catch (Exception e) { log.debug("Failed to read annotation values", e); } return -1; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassWriter.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.LinkedHashSet; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; /** * A hacked version of ClassWriter * allowing to plug-in instrumentation providers and instrument class in single invocation. Also, it * provides a smart and lightweight common supertype resolution method for computing frames. * *

The class is not thread-safe but since there is exactly one instance per instrumented class * there is no chance of parallel access ever happening. * * @author Jaroslav Bachorik */ final class BTraceClassWriter extends ClassWriter { private final Deque instrumentors = new ArrayDeque<>(); private final ClassLoader targetCL; private final BTraceClassReader cr; BTraceClassWriter(ClassLoader cl, int flags) { super(flags); targetCL = cl != null ? cl : ClassLoader.getSystemClassLoader(); cr = null; } BTraceClassWriter(ClassLoader cl, BTraceClassReader reader, int flags) { super(reader, flags); targetCL = cl != null ? cl : ClassLoader.getSystemClassLoader(); cr = reader; } public void addInstrumentor(BTraceProbe bp) { addInstrumentor(bp, null); } public void addInstrumentor(BTraceProbe bp, ClassLoader cl) { if (cr != null && bp != null) { Instrumentor top = instrumentors.peekLast(); ClassVisitor parent = top != null ? top : this; Instrumentor i = Instrumentor.create(cr, bp, parent, cl); if (i != null) { instrumentors.add(i); } } } public byte[] instrument() { boolean hit = false; if (instrumentors.isEmpty()) return null; Instrumentor top = instrumentors.peekLast(); ClassVisitor cv = top != null ? top : this; InstrumentUtils.accept(cr, cv); for (Instrumentor i : instrumentors) { hit |= i.hasMatch(); } return hit ? toByteArray() : null; } @Override protected String getCommonSuperClass(String type1, String type2) { // Using type closures resolved via the associate classloader LinkedHashSet type1Closure = new LinkedHashSet<>(); LinkedHashSet type2Closure = new LinkedHashSet<>(); InstrumentUtils.collectHierarchyClosure(targetCL, type1, type1Closure, true); InstrumentUtils.collectHierarchyClosure(targetCL, type2, type2Closure, true); // basically, do intersection type1Closure.retainAll(type2Closure); // if the intersection is not empty the first element is the closest common ancestor Iterator iter = type1Closure.iterator(); if (iter.hasNext()) { return iter.next(); } return Constants.OBJECT_INTERNAL; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodNode.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.MethodNode; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Where; import java.util.Comparator; import java.util.Set; /** * @author Jaroslav Bachorik */ public class BTraceMethodNode extends MethodNode { public static final Comparator COMPARATOR = (o1, o2) -> (o1.name + "#" + o1.desc).compareTo(o2.name + "#" + o2.desc); private final BTraceProbeNode cn; private final CallGraph graph; private final String methodId; private OnMethod om; private OnProbe op; private Location loc; private boolean sampled; private boolean isBTraceHandler; BTraceMethodNode(MethodNode from, BTraceProbeNode cn) { this(from, cn, false); } BTraceMethodNode(MethodNode from, BTraceProbeNode cn, boolean initBTraceHandler) { super( Opcodes.ASM9, from.access, from.name, from.desc, from.signature, from.exceptions.toArray(new String[0])); this.cn = cn; graph = cn.getGraph(); methodId = CallGraph.methodId(name, desc); isBTraceHandler = initBTraceHandler; } @Override public void visitEnd() { if (om != null) { verifySpecialParameters(om); cn.addOnMethod(om); } if (op != null) { cn.addOnProbe(op); } if (isBTraceHandler) { graph.addStarting(methodId); } super.visitEnd(); } @Override public AnnotationVisitor visitAnnotation(String type, boolean visible) { AnnotationVisitor av = super.visitAnnotation(type, visible); if (type.startsWith("Lorg/openjdk/btrace/core/annotations/")) { isBTraceHandler = true; } if (type.equals(Constants.ONMETHOD_DESC)) { om = new OnMethod(this); om.setTargetName(name); om.setTargetDescriptor(desc); return new AnnotationVisitor(Opcodes.ASM9, av) { @Override public void visit(String name, Object value) { super.visit(name, value); switch (name) { case "clazz": om.setClazz((String) value); break; case "method": om.setMethod((String) value); break; case "type": om.setType((String) value); break; case "exactTypeMatch": { om.setExactTypeMatch((boolean) value); break; } default: System.err.println("btrace WARNING: Unsupported @OnMethod attribute: " + name); } } @Override public AnnotationVisitor visitAnnotation(String name, String desc) { AnnotationVisitor av1 = super.visitAnnotation(name, desc); if (desc.equals(Constants.LOCATION_DESC)) { loc = new Location(); return new AnnotationVisitor(Opcodes.ASM9, av1) { @Override public void visitEnum(String name, String desc, String value) { super.visitEnum(name, desc, value); if (desc.equals(Constants.WHERE_DESC)) { loc.setWhere(Enum.valueOf(Where.class, value)); } else if (desc.equals(Constants.KIND_DESC)) { loc.setValue(Enum.valueOf(Kind.class, value)); } } @Override public void visit(String name, Object value) { super.visit(name, value); switch (name) { case "clazz": loc.setClazz((String) value); break; case "method": loc.setMethod((String) value); break; case "type": loc.setType((String) value); break; case "field": loc.setField((String) value); break; case "line": loc.setLine(((Number) value).intValue()); break; } } @Override public void visitEnd() { if (loc != null) { om.setLocation(loc); } super.visitEnd(); } }; } else if (desc.equals(Constants.LEVEL_DESC)) { return new AnnotationVisitor(Opcodes.ASM9, av1) { @Override public void visit(String name, Object value) { super.visit(name, value); if ("value".equals(name)) { om.setLevel(Level.fromString((String) value)); } } }; } return av1; } }; } else if (type.equals(Constants.ONPROBE_DESC)) { op = new OnProbe(this); op.setTargetName(name); op.setTargetDescriptor(desc); return new AnnotationVisitor(Opcodes.ASM9, av) { @Override public void visit(String name, Object value) { super.visit(name, value); switch (name) { case "namespace": op.setNamespace((String) value); break; case "name": op.setName((String) value); break; } } }; } else if (type.equals(Constants.SAMPLED_DESC)) { if (om != null) { om.setSamplerKind(Sampled.Sampler.Adaptive); return new AnnotationVisitor(Opcodes.ASM9, av) { private boolean meanSet = false; @Override public void visit(String name, Object value) { super.visit(name, value); if (name.equals("mean")) { om.setSamplerMean((Integer) value); meanSet = true; } } @Override public void visitEnum(String name, String desc, String value) { super.visitEnum(name, desc, value); if (name.equals("kind") && desc.equals(Constants.SAMPLER_DESC)) { om.setSamplerKind(Sampled.Sampler.valueOf(value)); } } @Override public void visitEnd() { if (!meanSet) { if (om.getSamplerKind() == Sampled.Sampler.Adaptive) { om.setSamplerMean(500); } else if (om.getSamplerKind() == Sampled.Sampler.Const) { om.setSamplerMean(Sampled.MEAN_DEFAULT); } } if (om.getSamplerKind() == Sampled.Sampler.Adaptive) { // the time frame for adaptive sampling // should be at least 180ns - // (80ns timestamps + 15ns stub) * 2 safety margin if (om.getSamplerMean() < 180) { System.err.println( "Setting the adaptive sampler time windows to the default of 180ns"); om.setSamplerMean(180); } } super.visitEnd(); } }; } sampled = true; } return av; } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { AnnotationVisitor av = super.visitParameterAnnotation(parameter, desc, visible); if (om != null) { av = setSpecialParameters(om, desc, parameter, av); } else if (op != null) { av = setSpecialParameters(op, desc, parameter, av); } return av; } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { owner = cn.translateOwner(owner); if (opcode == Opcodes.INVOKESTATIC) { graph.addEdge(methodId, CallGraph.methodId(name, desc)); } super.visitMethodInsn(opcode, owner, name, desc, itf); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { super.visitFieldInsn(opcode, cn.translateOwner(owner), name, desc); } public boolean isBcpRequired() { return isBTraceHandler && om == null && op == null; } public boolean isBTraceHandler() { return isBTraceHandler; } public OnMethod getOnMethod() { return om; } public boolean isSampled() { return sampled; } public Set getCallees() { return cn.callees(name, desc); } public Set getCallers() { return cn.callers(name, desc); } boolean isFieldInjected(String name) { return cn.isFieldInjected(name); } boolean isServiceType(String typeName) { return cn.isServiceType(typeName); } OnProbe getOnProbe() { return op; } private AnnotationVisitor setSpecialParameters( SpecialParameterHolder ph, String desc, int parameter, AnnotationVisitor av) { // for OnProbe the 'loc' variable will be null; we will need to verfiy the placement later on if (desc.equals(Constants.SELF_DESC)) { ph.setSelfParameter(parameter); } else if (desc.equals(Constants.BTRACE_PROBECLASSNAME_DESC)) { ph.setClassNameParameter(parameter); } else if (desc.equals(Constants.BTRACE_PROBEMETHODNAME_DESC)) { ph.setMethodParameter(parameter); av = new AnnotationVisitor(Opcodes.ASM9, av) { @Override public void visit(String name, Object val) { if (name.equals("fqn")) { ph.setMethodFqn((Boolean) val); } super.visit(name, val); } }; } else if (desc.equals(Constants.RETURN_DESC)) { ph.setReturnParameter(parameter); } else if (desc.equals(Constants.TARGETMETHOD_DESC)) { ph.setTargetMethodOrFieldParameter(parameter); av = new AnnotationVisitor(Opcodes.ASM9, av) { @Override public void visit(String name, Object val) { if (name.equals("fqn")) { ph.setTargetMethodOrFieldFqn((Boolean) val); } super.visit(name, val); } }; } else if (desc.equals(Constants.TARGETINSTANCE_DESC)) { ph.setTargetInstanceParameter(parameter); } else if (desc.equals(Constants.DURATION_DESC)) { ph.setDurationParameter(parameter); } return av; } private void verifySpecialParameters(OnMethod om) { Location loc = om.getLocation(); if (om.getReturnParameter() != -1) { if (!(loc.getValue() == Kind.RETURN || (loc.getValue() == Kind.CALL && loc.getWhere() == Where.AFTER) || (loc.getValue() == Kind.ARRAY_GET && loc.getWhere() == Where.AFTER) || (loc.getValue() == Kind.FIELD_GET && loc.getWhere() == Where.AFTER) || (loc.getValue() == Kind.NEW && loc.getWhere() == Where.AFTER) || (loc.getValue() == Kind.NEWARRAY && loc.getWhere() == Where.AFTER))) { Verifier.reportError( "return.desc.invalid", om.getTargetName() + om.getTargetDescriptor() + "(" + om.getReturnParameter() + ")"); } } if (om.getTargetMethodOrFieldParameter() != -1) { if (!(loc.getValue() == Kind.CALL || loc.getValue() == Kind.FIELD_GET || loc.getValue() == Kind.FIELD_SET || loc.getValue() == Kind.ARRAY_GET || loc.getValue() == Kind.ARRAY_SET)) { Verifier.reportError( "target-method.desc.invalid", om.getTargetName() + om.getTargetDescriptor() + "(" + om.getTargetMethodOrFieldParameter() + ")"); } } if (om.getTargetInstanceParameter() != -1) { if (!(loc.getValue() == Kind.CALL || loc.getValue() == Kind.FIELD_GET || loc.getValue() == Kind.FIELD_SET || loc.getValue() == Kind.ARRAY_GET || loc.getValue() == Kind.ARRAY_SET || loc.getValue() == Kind.INSTANCEOF || loc.getValue() == Kind.CHECKCAST || loc.getValue() == Kind.ERROR || loc.getValue() == Kind.THROW || loc.getValue() == Kind.CATCH || loc.getValue() == Kind.SYNC_ENTRY || loc.getValue() == Kind.SYNC_EXIT)) { Verifier.reportError( "target-instance.desc.invalid", om.getTargetName() + om.getTargetDescriptor() + "(" + om.getTargetInstanceParameter() + ")"); } } if (om.getDurationParameter() != -1) { if (!((loc.getValue() == Kind.RETURN || loc.getValue() == Kind.ERROR) || (loc.getValue() == Kind.CALL && loc.getWhere() == Where.AFTER))) { Verifier.reportError( "duration.desc.invalid", om.getTargetName() + om.getTargetDescriptor() + "(" + om.getDurationParameter() + ")"); } } } @Override public String toString() { return "BTraceMethodNode{name = " + name + ", desc=" + desc + '}'; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodVisitor.java ================================================ package org.openjdk.btrace.instr; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; public class BTraceMethodVisitor extends MethodVisitor { private final MethodInstrumentorHelper mHelper; public BTraceMethodVisitor(MethodVisitor mv, MethodInstrumentorHelper mHelper) { super(Opcodes.ASM9, mv); this.mHelper = mHelper; } public final int storeAsNew() { return mHelper.storeAsNew(); } public final int storeNewLocal(Type t) { int index = mHelper.newVar(t); super.visitVarInsn(t.getOpcode(Opcodes.ISTORE), index); return index; } public final void addTryCatchHandler(Label start, Label handler) { mHelper.addTryCatchHandler(start, handler); } public void insertFrameReplaceStack(Label l, Type... stack) { mHelper.insertFrameReplaceStack(l, stack); } public void insertFrameAppendStack(Label l, Type... stack) { mHelper.insertFrameAppendStack(l, stack); } public void insertFrameSameStack(Label l) { mHelper.insertFrameSameStack(l); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbe.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * * Copyright (c) 2017, 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.instr; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.util.Collection; import java.util.Set; import org.objectweb.asm.ClassVisitor; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.extensions.Permission; public interface BTraceProbe { /** * Returns the action method prefix for this probe. * This is computed once and cached. * * Format: BTRACE_METHOD_PREFIX + className.replace('/', '$') + "$" * Example: "$btrace$com$example$MyProbe$" * * @return cached action prefix */ String getActionPrefix(); Collection getApplicableHandlers(BTraceClassReader cr); byte[] getFullBytecode(); byte[] getDataHolderBytecode(); String getClassName(); String getClassName(boolean internal); boolean isClassRenamed(); boolean isTransforming(); boolean isVerified(); void notifyTransform(String className); Iterable onmethods(); Iterable onprobes(); Class register(BTraceRuntime.Impl rt, BTraceTransformer t); /** * @return the defined probe {@link Class}, or {@code null} if the probe has not been * registered (or has been unregistered). */ Class getProbeClass(); void unregister(); boolean willInstrument(Class clz); void checkVerified(); void copyHandlers(ClassVisitor copyingVisitor); void applyArgs(ArgsMap argsMap); BTraceRuntime.Impl getRuntime(); /** * Returns the set of permissions required by this probe. * * @return unmodifiable set of required permissions */ Set getRequiredPermissions(); /** * Look up a previously cached {@link MethodHandle} for a handler on this probe. * *

The cache is per-probe so it dies naturally when the probe object is collected — * no cross-probe scan required on unregister. Returns {@code null} on miss, or when * the probe implementation has no cache (e.g. stub probes in tests). */ default MethodHandle getCachedHandler(String handlerName, MethodType type) { return null; } /** * Store a resolved handler {@link MethodHandle} for subsequent lookups. Implementations * without a backing cache (e.g. stub probes in tests) may silently drop the entry. */ default void cacheHandler(String handlerName, MethodType type, MethodHandle mh) { // no-op by default } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeFactory.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * * Copyright (c) 2017, 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.instr; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.SharedSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A factory class for {@link BTraceProbeNode} instances * * @author Jaroslav Bachorik */ public final class BTraceProbeFactory { private static final Logger log = LoggerFactory.getLogger(BTraceProbeFactory.class); private static final int CLASS_MAGIC = 0xCAFEBABE; private final SharedSettings settings; public BTraceProbeFactory(SharedSettings settings) { this.settings = settings; } private static void applyArgs(BTraceProbe bp, ArgsMap argsMap) { if (bp != null && argsMap != null && !argsMap.isEmpty()) { bp.applyArgs(argsMap); } } /** * Check if a particular file can be loaded as a BTrace probe. Currently only the plain class file * and BTrace probe pack are supported. * * @param filePath the file path * @return {@literal true} if a BTrace probe can be reconstructed from data in the given file */ public static boolean canLoad(String filePath) { return canLoad(filePath, null); } public static boolean canLoad(String filePath, ClassLoader cl) { if (filePath == null) { return false; } Path path = Paths.get(filePath); InputStream is = null; try { if (!Files.exists(path)) { // try to load from the classpath if (cl == null) { cl = ClassLoader.getSystemClassLoader(); } is = cl.getResourceAsStream("META-INF/btrace/" + filePath); } else { is = Files.newInputStream(path); } if (is != null) { try { try (DataInputStream dis = new DataInputStream(is)) { int magic = dis.readInt(); return magic == CLASS_MAGIC || magic == BTraceProbePersisted.MAGIC; } } catch (IOException ignored) { is = null; } } } catch (IOException ignored) { } finally { if (is != null) { try { is.close(); } catch (IOException ignored) { } } } return false; } SharedSettings getSettings() { return settings; } public BTraceProbe createProbe(byte[] code) { return createProbe(code, null); } public BTraceProbe createProbe(byte[] code, ArgsMap argsMap) { BTraceProbe bp = null; int mgc = ((code[0] & 0xff) << 24) | ((code[1] & 0xff) << 16) | ((code[2] & 0xff) << 8) | ((code[3] & 0xff)); if (mgc == BTraceProbePersisted.MAGIC) { BTraceProbePersisted bpp = new BTraceProbePersisted(this); try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(Arrays.copyOfRange(code, 4, code.length)))) { bpp.read(dis); bp = bpp; } catch (IOException e) { log.debug("Failed to read BTrace pack", e); } } else { bp = new BTraceProbeNode(this, code); } applyArgs(bp, argsMap); return bp; } public BTraceProbe createProbe(InputStream code) { return createProbe(code, null); } public BTraceProbe createProbe(InputStream code, ArgsMap argsMap) { BTraceProbe bp = null; try (DataInputStream dis = new DataInputStream(code)) { dis.mark(0); int mgc = dis.readInt(); if (mgc == BTraceProbePersisted.MAGIC) { BTraceProbePersisted bpp = new BTraceProbePersisted(this); bpp.read(dis); bp = bpp; } else { code.reset(); bp = new BTraceProbeNode(this, code); } } catch (IOException e) { log.debug("Failed to create a probe", e); } applyArgs(bp, argsMap); return bp; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeNode.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.DebugSupport; import org.openjdk.btrace.core.Messages; import org.openjdk.btrace.core.VerifierException; import org.openjdk.btrace.core.comm.RetransformClassNotification; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.extension.ServiceDeclarationRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * @author Jaroslav Bachorik */ public final class BTraceProbeNode extends ClassNode implements BTraceProbe { private static final Logger log = LoggerFactory.getLogger(BTraceProbeNode.class); final BTraceProbeSupport delegate; final BTraceProbeFactory factory; final DebugSupport debug; private final CallGraph graph; private final Map idmap; private final Set jfrHandlers = new HashSet<>(); private final Preprocessor prep; private final BTraceBCPClassLoader bcpResourceClassLoader; private volatile BTraceRuntime.Impl rt = null; private BTraceTransformer transformer; private VerifierException verifierException = null; private BTraceProbeNode(BTraceProbeFactory factory) { super(Opcodes.ASM9); this.factory = factory; bcpResourceClassLoader = new BTraceBCPClassLoader(factory.getSettings()); debug = new DebugSupport(factory.getSettings()); delegate = new BTraceProbeSupport(); idmap = new HashMap<>(); graph = new CallGraph(); prep = new Preprocessor(); } BTraceProbeNode(BTraceProbeFactory factory, byte[] code) { this(factory); initialize(code); } BTraceProbeNode(BTraceProbeFactory factory, InputStream code) throws IOException { this(factory); initialize(code); } @Override public boolean isTransforming() { return delegate.isTransforming(); } @Override public void visit( int version, int access, String name, String sig, String superType, String[] itfcs) { delegate.setClassName(name); super.visit(version, access, delegate.getClassName(true), sig, superType, itfcs); } @Override public MethodVisitor visitMethod( int access, String name, String desc, String sig, String[] exceptions) { super.visitMethod(access, name, desc, sig, exceptions); MethodNode mn = methods.remove(methods.size() - 1); BTraceMethodNode bmn = new BTraceMethodNode(mn, this, jfrHandlers.contains(name)); methods.add(bmn); idmap.put(CallGraph.methodId(name, desc), bmn); return isTrusted() ? bmn : new MethodVerifier( bmn, access, delegate.getOrigName(), name, desc, bcpResourceClassLoader); } @Override public FieldVisitor visitField( int access, String name, String desc, String signature, Object value) { return new FieldVisitor(Opcodes.ASM9, super.visitField(access, name, desc, signature, value)) { @Override public AnnotationVisitor visitAnnotation(String type, boolean aVisible) { AnnotationVisitor av = super.visitAnnotation(type, aVisible); if (type.equals(Constants.INJECTED_DESC)) { // Bytecode-time validation for @Injected fields: // This ASM-based check enforces that the injected service type is declared by // some available extension (as exposed via ServiceDeclarationRegistry). It does // not load classes; instead, the agent registers a resolver that knows about // extension manifests. This complements the runtime validation in // org.openjdk.btrace.agent.Client#validateDeclaredServices, which uses reflection // to account for classloader identity, JPMS access, and linkage in the actual // target JVM. String internal = Type.getType(desc).getInternalName(); String fqcn = internal.replace('/', '.'); if (!ServiceDeclarationRegistry.isDeclaredService(fqcn)) { throw new VerifierException(Messages.get("invalid.injected.service") + ": " + fqcn); } delegate.addServiceField(name, internal); } if (type.equals("Lorg/openjdk/btrace/core/annotations/Event;")) { av = new AnnotationVisitor(Opcodes.ASM8, av) { @Override public void visit(String name, Object value) { if (name.equals("handler") && value instanceof String) { jfrHandlers.add((String) value); } super.visit(name, value); } }; } return av; } }; } @Override public Collection getApplicableHandlers(BTraceClassReader cr) { return delegate.getApplicableHandlers(cr); } @Override public Iterable onmethods() { return delegate.onmethods(); } public Collection getOnMethods() { return delegate.getOnMethods(); } @Override public Iterable onprobes() { return delegate.onprobes(); } @Override public String getClassName() { return getClassName(false); } @Override public String getClassName(boolean internal) { return delegate.getClassName(internal); } String translateOwner(String owner) { return delegate.translateOwner(owner); } @Override public Class register(BTraceRuntime.Impl rt, BTraceTransformer t) { byte[] code = getBytecode(true); if (debug.isDumpClasses()) { debug.dumpClass(name + "_bcp", code); } Class clz = delegate.defineClass(rt, code); HandlerRepositoryImpl.registerProbe(this); t.register(this); transformer = t; this.rt = rt; return clz; } @Override public Class getProbeClass() { return delegate.getProbeClass(); } @Override public java.lang.invoke.MethodHandle getCachedHandler( String handlerName, java.lang.invoke.MethodType type) { return delegate.getCachedHandler(handlerName, type); } @Override public void cacheHandler( String handlerName, java.lang.invoke.MethodType type, java.lang.invoke.MethodHandle mh) { delegate.cacheHandler(handlerName, type, mh); } @Override public void unregister() { HandlerRepositoryImpl.unregisterProbe(this); if (transformer != null && isTransforming()) { if (log.isDebugEnabled()) { log.debug("onExit: removing transformer for {}", getClassName()); } transformer.unregister(this); } delegate.clearProbeClass(); rt = null; } @Override public byte[] getFullBytecode() { return getBytecode(false); } @Override public byte[] getDataHolderBytecode() { return getBytecode(true); } @Override public BTraceRuntime.Impl getRuntime() { return rt; } private byte[] getBytecode(boolean onlyBcpMethods) { ClassWriter cw = InstrumentUtils.newClassWriter(true); ClassVisitor cv = cw; if (onlyBcpMethods) { cv = new ClassVisitor(Opcodes.ASM9, cw) { @Override public MethodVisitor visitMethod( int access, String name, String desc, String sig, String[] exceptions) { if (name.startsWith("<")) { // never check constructor and static initializer return super.visitMethod(access, name, desc, sig, exceptions); } BTraceMethodNode bmn = idmap.get(CallGraph.methodId(name, desc)); if (bmn != null) { // Include BCP-required methods AND probe handler methods: // Handlers are invoked via INVOKEDYNAMIC (IndyDispatcher.bootstrap), // so the handler method body must be present in the bootstrap-CL probe class. // This applies to @OnMethod handlers (om != null) and @OnProbe handlers // (op != null — mapped to @OnMethod entries via mapOnProbes()). boolean isHandler = bmn.getOnMethod() != null || bmn.getOnProbe() != null; if (bmn.isBcpRequired() || isHandler) { // Handlers: rewrite descriptor AnyType → Object to match the INDY call site // type (Instrumentor.invokeBTraceAction replaces AnyType with Object in // the INDY descriptor for JVM stack compatibility). String effectiveDesc = isHandler ? desc.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC) : desc; return super.visitMethod(access, name, effectiveDesc, sig, exceptions); } for (BTraceMethodNode c : bmn.getCallers()) { boolean callerIsHandler = c.getOnMethod() != null || c.getOnProbe() != null; if (c.isBcpRequired() || callerIsHandler) { return super.visitMethod(access, name, desc, sig, exceptions); } } return null; } return super.visitMethod(access, name, desc, sig, exceptions); } }; } accept(cv); return cw.toByteArray(); } /** * Collects all the methods reachable from this particular method * * @param name the method name * @param desc the method descriptor * @return the callee reachability closure */ Set callees(String name, String desc) { Set closure = new HashSet<>(); graph.callees(name, desc, closure); return fromIdSet(closure); } /** * Collects all the methods from which this particular method is reachable * * @param name the method name * @param desc the method descriptor * @return the caller reachability closure */ Set callers(String name, String desc) { Set closure = new HashSet<>(); graph.callers(name, desc, closure); return fromIdSet(closure); } @Override public boolean willInstrument(Class clz) { return delegate.willInstrument(clz); } @Override public boolean isClassRenamed() { return delegate.isClassRenamed(); } @Override public boolean isVerified() { return verifierException == null; } @Override public String getActionPrefix() { return delegate.getActionPrefix(); } private VerifierException getVerifierException() { return verifierException; } boolean isFieldInjected(String name) { return delegate.isFieldInjected(name); } boolean isServiceType(String typeName) { return delegate.isServiceType(typeName); } void addOnMethod(OnMethod om) { delegate.addOnMethod(om); } void addOnProbe(OnProbe op) { delegate.addOnProbe(op); } void addRequiredPermission(Permission permission) { delegate.addRequiredPermission(permission); } @Override public Set getRequiredPermissions() { return delegate.getRequiredPermissions(); } void setTrusted() { delegate.setTrusted(); } boolean isTrusted() { return delegate.isTrusted(); } CallGraph getGraph() { return graph; } @Override public void notifyTransform(String className) { if (rt != null && factory.getSettings().isTrackRetransforms()) { rt.sendCommand(new RetransformClassNotification(className.replace('/', '.'))); } } @Override public void checkVerified() { if (!isVerified()) { throw getVerifierException(); } } @Override public void copyHandlers(ClassVisitor copyingVisitor) { Set copyNodes = new TreeSet<>(BTraceMethodNode.COMPARATOR); for (OnMethod om : onmethods()) { if (!om.isCalled()) { continue; } BTraceMethodNode bmn = om.getMethodNode(); MethodNode mn = copy(bmn); copyNodes.add(mn); for (BTraceMethodNode c : bmn.getCallees()) { copyNodes.add(copy(c)); } } copyingVisitor.visit( Opcodes.V1_7, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, getClassName(true), null, "java/lang/Object", null); for (MethodNode mn : copyNodes) { mn.accept(copyingVisitor); } copyingVisitor.visitEnd(); } @Override public void applyArgs(ArgsMap argsMap) { delegate.applyArgs(argsMap); } /** Maps a list of @OnProbe's to a list @OnMethod's using probe descriptor XML files. */ private void mapOnProbes() { ProbeDescriptorLoader pdl = getProbeDescriptorLoader(); for (OnProbe op : delegate.onprobes()) { String ns = op.getNamespace(); if (log.isDebugEnabled()) { log.debug("about to load probe descriptor for namespace {}", ns); } // load probe descriptor for this namespace ProbeDescriptor probeDesc = pdl.load(ns); if (probeDesc == null) { if (log.isDebugEnabled()) { log.debug("failed to find probe descriptor for namespace {}", ns); } continue; } // find particular probe mappings using "local" name OnProbe foundProbe = probeDesc.findProbe(op.getName()); if (foundProbe == null) { if (log.isDebugEnabled()) { log.debug("no probe mappings for {}", op.getName()); } continue; } if (log.isDebugEnabled()) { log.debug("found probe mappings for {}", op.getName()); } Collection omColl = foundProbe.getOnMethods(); for (OnMethod om : omColl) { // copy the info in a new OnMethod so that // we can set target method name and descriptor // Note that the probe descriptor cache is used // across BTrace sessions. So, we should not update // cached OnProbes (and their OnMethods). OnMethod omn = new OnMethod(op.getMethodNode()); omn.copyFrom(om); omn.setTargetName(op.getTargetName()); omn.setTargetDescriptor(op.getTargetDescriptor()); omn.setClassNameParameter(op.getClassNameParameter()); omn.setMethodParameter(op.getMethodParameter()); omn.setDurationParameter(op.getDurationParameter()); omn.setMethodFqn(op.isMethodFqn()); omn.setReturnParameter(op.getReturnParameter()); omn.setSelfParameter(op.getSelfParameter()); omn.setTargetInstanceParameter(op.getTargetInstanceParameter()); omn.setTargetMethodOrFieldFqn(op.isTargetMethodOrFieldFqn()); omn.setTargetMethodOrFieldParameter(op.getTargetMethodOrFieldParameter()); addOnMethod(omn); } } } private ProbeDescriptorLoader getProbeDescriptorLoader() { String path = factory.getSettings().getProbeDescPath(); return new ProbeDescriptorLoader(path); } private void initialize(byte[] code) { ClassReader cr = new ClassReader(code); if (debug.isDumpClasses()) { debug.dumpClass(cr.getClassName() + "_orig", code); } initialize(cr); } private void initialize(InputStream code) throws IOException { initialize(readFully(code)); } private void initialize(ClassReader cr) { try { Verifier v = new Verifier(this, factory.getSettings().isTrusted()); log.debug("verifying BTrace class ..."); cr.accept(v, ClassReader.SKIP_DEBUG); if (log.isDebugEnabled()) { String clzName = getClassName(); log.debug("BTrace class {} verified", clzName); log.debug("preprocessing BTrace class {} ...", clzName); } prep.process(this); log.debug("... preprocessed"); try { Class.forName("javax.xml.bind.JAXBException"); mapOnProbes(); } catch (ClassNotFoundException e) { log.debug("XML bindings are missing. @OnProbe support is disabled."); } } catch (VerifierException e) { verifierException = e; } finally { if (debug.isDumpClasses() && name != null) { debug.dumpClass(name, getBytecode(false)); } } } private Set fromIdSet(Set ids) { Set methods = new HashSet<>(); for (String id : ids) { BTraceMethodNode mn = idmap.get(id); if (mn != null) { methods.add(mn); } } return methods; } private MethodNode copy(MethodNode n) { String[] exceptions = n.exceptions != null ? n.exceptions.toArray(new String[0]) : null; MethodNode mn = new MethodNode(Opcodes.ASM9, n.access, n.name, n.desc, n.signature, exceptions); n.accept(mn); mn.access = Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE; mn.desc = mn.desc.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC); mn.signature = mn.signature != null ? mn.signature.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC) : null; mn.name = getActionPrefix() + mn.name; return mn; } private byte[] readFully(InputStream is) throws IOException { int bufSize = 512; int pos = 0; byte[] finArr = new byte[1024]; byte[] buff = new byte[bufSize]; int read = 0; while ((read = is.read(buff, 0, bufSize)) > 0) { int newpos = pos + read; if (newpos >= finArr.length) { finArr = Arrays.copyOf(finArr, finArr.length * 2); } System.arraycopy(buff, 0, finArr, pos, read); pos = newpos; } return Arrays.copyOfRange(finArr, 0, pos); } @Override public String toString() { return "BTraceProbe{" + "delegate=" + delegate + '}'; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbePersisted.java ================================================ /* * Copyright (c) 2017, 2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.instr; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.DebugSupport; import org.openjdk.btrace.core.VerifierException; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Where; import org.openjdk.btrace.core.comm.RetransformClassNotification; import org.openjdk.btrace.core.extensions.Permission; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import static org.objectweb.asm.Opcodes.AASTORE; import static org.objectweb.asm.Opcodes.ACC_ENUM; import static org.objectweb.asm.Opcodes.ACC_INTERFACE; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_SYNCHRONIZED; import static org.objectweb.asm.Opcodes.ANEWARRAY; import static org.objectweb.asm.Opcodes.ASM9; import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.BASTORE; import static org.objectweb.asm.Opcodes.CASTORE; import static org.objectweb.asm.Opcodes.DASTORE; import static org.objectweb.asm.Opcodes.FASTORE; import static org.objectweb.asm.Opcodes.IASTORE; import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.LASTORE; import static org.objectweb.asm.Opcodes.MONITORENTER; import static org.objectweb.asm.Opcodes.MONITOREXIT; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.NEWARRAY; import static org.objectweb.asm.Opcodes.PUTFIELD; import static org.objectweb.asm.Opcodes.PUTSTATIC; import static org.objectweb.asm.Opcodes.RET; import static org.objectweb.asm.Opcodes.SASTORE; public class BTraceProbePersisted implements BTraceProbe { private static final Logger log = LoggerFactory.getLogger(BTraceProbePersisted.class); static final int MAGIC = 0xbacecaca; private static final int VERSION = 3; final BTraceProbeSupport delegate; private final BTraceProbeFactory factory; private final DebugSupport debug; private final AtomicBoolean triedVerify = new AtomicBoolean(false); private final Map> calleeMap = new HashMap<>(); private volatile BTraceRuntime.Impl rt = null; private BTraceTransformer transformer; private byte[] fullData = null; private byte[] dataHolder = null; private boolean preverified; BTraceProbePersisted(BTraceProbeFactory f) { this(f, null); } private BTraceProbePersisted(BTraceProbeFactory f, BTraceProbeSupport delegate) { this.debug = new DebugSupport(f.getSettings()); this.delegate = delegate != null ? delegate : new BTraceProbeSupport(); factory = f; preverified = false; } private BTraceProbePersisted(BTraceProbeNode bpn) { this(bpn.factory, bpn.delegate); fullData = bpn.getFullBytecode(); dataHolder = bpn.getDataHolderBytecode(); loadCalleeMap(bpn, calleeMap); preverified = true; } public static BTraceProbePersisted from(BTraceProbe bp) { return bp instanceof BTraceProbePersisted ? (BTraceProbePersisted) bp : new BTraceProbePersisted((BTraceProbeNode) bp); } private static void loadCalleeMap(BTraceProbeNode bpn, Map> cMap) { Set roots = new HashSet<>(); for (OnMethod om : bpn.onmethods()) { roots.add(new Handler(om.getTargetName(), om.getTargetDescriptor())); } for (OnProbe op : bpn.onprobes()) { roots.add(new Handler(op.getTargetName(), op.getTargetDescriptor())); } for (Handler h : roots) { String rootKey = CallGraph.methodId(h.name, h.desc); Set cs = cMap.computeIfAbsent(rootKey, k -> new HashSet<>()); for (BTraceMethodNode bmn : bpn.callees(h.name, h.desc)) { cs.add(CallGraph.methodId(bmn.name, bmn.desc)); } } } private static String getClazz(OnMethod om) { String clzName = om.getClazz(); if (om.isSubtypeMatcher()) { return "+" + om.getClazz(); } else { if (om.isClassRegexMatcher()) { clzName = "/" + clzName + "/"; } if (om.isClassAnnotationMatcher()) { clzName = "@" + clzName; } } return clzName; } private static String getMethod(OnMethod om) { String mName = om.getMethod(); if (om.isMethodRegexMatcher()) { mName = "/" + mName + "/"; } if (om.isMethodAnnotationMatcher()) { mName = "@" + mName; } return mName; } public void read(DataInputStream dis) throws IOException { int version = dis.readInt(); switch (version) { case 1: { read_1(dis); break; } case 2: { read_2(dis); break; } case 3: { read_3(dis); break; } default: { throw new IOException("Unsupported version for persisted probe: " + version); } } } /** * Read in the structure for version 1 * * @param dis data input stream * @throws IOException */ private void read_1(DataInputStream dis) throws IOException { delegate.setClassName(dis.readUTF()); readServices(dis); readOnMethods(dis); readOnProbes(dis); readCallees(dis); readDataHolderClass(dis); readFullData(dis); upgradeBytecode(); } /** * Read in the structure for version 2 * * @param dis data input stream * @throws IOException */ private void read_2(DataInputStream dis) throws IOException { delegate.setClassName(dis.readUTF()); readServices(dis); readOnMethods(dis); readOnProbes(dis); readCallees(dis); readDataHolderClass(dis); readFullData(dis); } /** * Read in the structure for version 3 * * @param dis data input stream * @throws IOException */ private void read_3(DataInputStream dis) throws IOException { delegate.setClassName(dis.readUTF()); readServices(dis); readOnMethods(dis); readOnProbes(dis); readCallees(dis); readPermissions(dis); readDataHolderClass(dis); readFullData(dis); } public void write(DataOutputStream dos) { try { dos.writeInt(MAGIC); dos.writeInt(VERSION); dos.writeUTF(getClassName(true)); writeServices(dos); writeOnMethods(dos); writeOnProbes(dos); writeCallees(dos); writePermissions(dos); writeDataHolderClass(dos); writeFullData(dos); } catch (IOException e) { log.debug("Failed to write probe {}", getClassName(), e); } } private void readServices(DataInputStream dis) throws IOException { int num = dis.readInt(); for (int i = 0; i < num; i++) { delegate.addServiceField(dis.readUTF(), dis.readUTF()); } } private void readOnMethods(DataInputStream dis) throws IOException { int num = dis.readInt(); for (int i = 0; i < num; i++) { OnMethod om = new OnMethod(); om.setClazz(dis.readUTF()); om.setMethod(dis.readUTF()); om.setExactTypeMatch(dis.readBoolean()); om.setTargetDescriptor(dis.readUTF()); om.setTargetName(dis.readUTF()); om.setType(dis.readUTF()); om.setClassNameParameter(dis.readInt()); om.setDurationParameter(dis.readInt()); om.setMethodParameter(dis.readInt()); om.setReturnParameter(dis.readInt()); om.setSelfParameter(dis.readInt()); om.setTargetInstanceParameter(dis.readInt()); om.setTargetMethodOrFieldParameter(dis.readInt()); om.setMethodFqn(dis.readBoolean()); om.setTargetMethodOrFieldFqn(dis.readBoolean()); om.setSamplerKind(Sampled.Sampler.valueOf(dis.readUTF())); om.setSamplerMean(dis.readInt()); om.setLevel(dis.readBoolean() ? Level.fromString(dis.readUTF()) : null); Location loc = new Location(); loc.setValue(Kind.valueOf(dis.readUTF())); loc.setWhere(Where.valueOf(dis.readUTF())); loc.setClazz(dis.readBoolean() ? dis.readUTF() : null); loc.setField(dis.readBoolean() ? dis.readUTF() : null); loc.setMethod(dis.readBoolean() ? dis.readUTF() : null); loc.setType(dis.readBoolean() ? dis.readUTF() : null); loc.setLine(dis.readInt()); om.setLocation(loc); delegate.addOnMethod(om); } } private void readOnProbes(DataInputStream dis) throws IOException { int num = dis.readInt(); for (int i = 0; i < num; i++) { OnProbe op = new OnProbe(); op.setNamespace(dis.readUTF()); op.setName(dis.readUTF()); op.setTargetDescriptor(dis.readUTF()); op.setTargetName(dis.readUTF()); op.setClassNameParameter(dis.readInt()); op.setDurationParameter(dis.readInt()); op.setMethodParameter(dis.readInt()); op.setReturnParameter(dis.readInt()); op.setSelfParameter(dis.readInt()); op.setTargetInstanceParameter(dis.readInt()); op.setTargetMethodOrFieldParameter(dis.readInt()); op.setMethodFqn(dis.readBoolean()); op.setTargetMethodOrFieldFqn(dis.readBoolean()); delegate.addOnProbe(op); } } private void readFullData(DataInputStream dis) throws IOException { int fullDataLen = dis.readInt(); fullData = new byte[fullDataLen]; dis.readFully(fullData); if (fullData.length > 0 && isClassRenamed()) { fullData = ProbeRenameVisitor.rename(getClassName(), fullData); } } private void readDataHolderClass(DataInputStream dis) throws IOException { int holderLen = dis.readInt(); dataHolder = new byte[holderLen]; dis.readFully(dataHolder); if (dataHolder.length > 0 && isClassRenamed()) { dataHolder = ProbeRenameVisitor.rename(getClassName(), dataHolder); } } private void readCallees(DataInputStream dis) throws IOException { int cnt = dis.readInt(); for (int i = 0; i < cnt; i++) { String from = dis.readUTF(); Set calleeSet = calleeMap.computeIfAbsent(from, k -> new HashSet<>()); int callees = dis.readInt(); for (int j = 0; j < callees; j++) { String to = dis.readUTF(); calleeSet.add(to); } } } private void readPermissions(DataInputStream dis) throws IOException { int cnt = dis.readInt(); for (int i = 0; i < cnt; i++) { String permName = dis.readUTF(); try { delegate.addRequiredPermission(Permission.valueOf(permName)); } catch (IllegalArgumentException e) { log.warn("Unknown permission in probe: {}", permName); } } } private void writeServices(DataOutputStream dos) throws IOException { Map svcFields = delegate.serviceFields(); dos.writeInt(svcFields.size()); for (Map.Entry e : svcFields.entrySet()) { dos.writeUTF(e.getKey()); dos.writeUTF(e.getValue()); } } private void writeOnMethods(DataOutputStream dos) throws IOException { Collection onMethods = delegate.getOnMethods(); int cnt = onMethods.size(); dos.writeInt(cnt); for (OnMethod om : onMethods) { dos.writeUTF(getClazz(om)); dos.writeUTF(getMethod(om)); dos.writeBoolean(om.isExactTypeMatch()); dos.writeUTF(om.getTargetDescriptor()); dos.writeUTF(om.getTargetName()); dos.writeUTF(om.getType()); dos.writeInt(om.getClassNameParameter()); dos.writeInt(om.getDurationParameter()); dos.writeInt(om.getMethodParameter()); dos.writeInt(om.getReturnParameter()); dos.writeInt(om.getSelfParameter()); dos.writeInt(om.getTargetInstanceParameter()); dos.writeInt(om.getTargetMethodOrFieldParameter()); dos.writeBoolean(om.isMethodFqn()); dos.writeBoolean(om.isTargetMethodOrFieldFqn()); dos.writeUTF(om.getSamplerKind().name()); dos.writeInt(om.getSamplerMean()); dos.writeBoolean(om.getLevel() != null); if (om.getLevel() != null) { dos.writeUTF(om.getLevel().getValue().toString()); } Location loc = om.getLocation(); dos.writeUTF(loc.getValue().name()); dos.writeUTF(loc.getWhere().name()); dos.writeBoolean(loc.getClazz() != null); if (loc.getClazz() != null) { dos.writeUTF(loc.getClazz()); } dos.writeBoolean(loc.getField() != null); if (loc.getField() != null) { dos.writeUTF(loc.getField()); } dos.writeBoolean(loc.getMethod() != null); if (loc.getMethod() != null) { dos.writeUTF(loc.getMethod()); } dos.writeBoolean(loc.getType() != null); if (loc.getType() != null) { dos.writeUTF(loc.getType()); } dos.writeInt(loc.getLine()); } } private void writeOnProbes(DataOutputStream dos) throws IOException { Collection onProbes = delegate.getOnProbes(); int cnt = onProbes.size(); dos.writeInt(cnt); for (OnProbe op : onProbes) { dos.writeUTF(op.getNamespace()); dos.writeUTF(op.getName()); dos.writeUTF(op.getTargetDescriptor()); dos.writeUTF(op.getTargetName()); dos.writeInt(op.getClassNameParameter()); dos.writeInt(op.getDurationParameter()); dos.writeInt(op.getMethodParameter()); dos.writeInt(op.getReturnParameter()); dos.writeInt(op.getSelfParameter()); dos.writeInt(op.getTargetInstanceParameter()); dos.writeInt(op.getTargetMethodOrFieldParameter()); dos.writeBoolean(op.isMethodFqn()); dos.writeBoolean(op.isTargetMethodOrFieldFqn()); } } private void writeFullData(DataOutputStream dos) throws IOException { dos.writeInt(fullData.length); dos.write(fullData); } private void writeDataHolderClass(DataOutputStream dos) throws IOException { dos.writeInt(dataHolder.length); dos.write(dataHolder); } private void writeCallees(DataOutputStream dos) throws IOException { int cnt = 0; for (Set callees : calleeMap.values()) { if (!callees.isEmpty()) { cnt++; } } dos.writeInt(cnt); for (Map.Entry> e : calleeMap.entrySet()) { if (!e.getValue().isEmpty()) { dos.writeUTF(e.getKey()); dos.writeInt(e.getValue().size()); for (String c : e.getValue()) { dos.writeUTF(c); } } } } private void writePermissions(DataOutputStream dos) throws IOException { Set perms = delegate.getRequiredPermissions(); dos.writeInt(perms.size()); for (Permission perm : perms) { dos.writeUTF(perm.name()); } } @Override public Collection getApplicableHandlers(BTraceClassReader cr) { return delegate.getApplicableHandlers(cr); } @Override public byte[] getFullBytecode() { return fullData; } @Override public byte[] getDataHolderBytecode() { return dataHolder; } @Override public String getClassName() { return delegate.getClassName(false); } @Override public String getClassName(boolean internal) { return delegate.getClassName(internal); } @Override public boolean isClassRenamed() { return delegate.isClassRenamed(); } @Override public boolean isTransforming() { return delegate.isTransforming(); } @Override public boolean isVerified() { if (factory.getSettings().isTrusted()) { return true; } if (triedVerify.compareAndSet(false, true)) { try { verifyBytecode(); return true; } catch (VerifierException e) { if (Boolean.getBoolean("btrace.verifier.dump")) { System.err.println("[BTRACE VERIFY] " + e.getMessage()); } log.debug("Class '{}' verification failed", getClassName(), e); } } return false; } @Override public void notifyTransform(String className) { if (rt != null && factory.getSettings().isTrackRetransforms()) { rt.sendCommand(new RetransformClassNotification(className.replace('/', '.'))); } } @Override public Iterable onmethods() { return delegate.onmethods(); } public Collection getOnMethods() { return delegate.getOnMethods(); } @Override public Iterable onprobes() { return delegate.onprobes(); } @Override public Class register(BTraceRuntime.Impl rt, BTraceTransformer t) { byte[] code = dataHolder; if (debug.isDumpClasses()) { debug.dumpClass(delegate.getClassName(true) + "_bcp", code); } Class clz = delegate.defineClass(rt, code); HandlerRepositoryImpl.registerProbe(this); t.register(this); transformer = t; this.rt = rt; return clz; } @Override public Class getProbeClass() { return delegate.getProbeClass(); } @Override public java.lang.invoke.MethodHandle getCachedHandler( String handlerName, java.lang.invoke.MethodType type) { return delegate.getCachedHandler(handlerName, type); } @Override public void cacheHandler( String handlerName, java.lang.invoke.MethodType type, java.lang.invoke.MethodHandle mh) { delegate.cacheHandler(handlerName, type, mh); } @Override public void unregister() { HandlerRepositoryImpl.unregisterProbe(this); if (transformer != null && isTransforming()) { if (log.isDebugEnabled()) { log.debug("onExit: removing transformer for {}", getClassName()); } transformer.unregister(this); } delegate.clearProbeClass(); rt = null; } @Override public boolean willInstrument(Class clz) { return delegate.willInstrument(clz); } @Override public void checkVerified() { if (!preverified) { isVerified(); } } @Override public void copyHandlers(ClassVisitor copyingVisitor) { ClassReader cr = new ClassReader(fullData); Set copiedMethods = new HashSet<>(); for (OnMethod om : onmethods()) { if (om.isCalled()) { String mid = CallGraph.methodId(om.getTargetName(), om.getTargetDescriptor()); copiedMethods.add(mid); Set callees = calleeMap.get(mid); if (callees != null) { copiedMethods.addAll(calleeMap.get(mid)); } } } cr.accept( new ClassVisitor(ASM9) { @Override public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { copyingVisitor.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { String mid = CallGraph.methodId(name, desc); if (copiedMethods.contains(mid)) { return copyingVisitor.visitMethod( ACC_PRIVATE | ACC_STATIC, getActionPrefix() + name, desc.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC), signature != null ? signature.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC) : null, exceptions); } return super.visitMethod(access, name, desc, signature, exceptions); } }, 0); } @Override public void applyArgs(ArgsMap argsMap) { delegate.applyArgs(argsMap); } @Override public BTraceRuntime.Impl getRuntime() { return rt; } @Override public String getActionPrefix() { return delegate.getActionPrefix(); } @Override public Set getRequiredPermissions() { return delegate.getRequiredPermissions(); } private void upgradeBytecode() { fullData = ProbeUpgradeVisitor_1_2.upgrade(new ClassReader(fullData)); dataHolder = ProbeUpgradeVisitor_1_2.upgrade(new ClassReader(dataHolder)); } private void verifyBytecode() throws VerifierException { ClassReader cr = new ClassReader(fullData); cr.accept( new ClassVisitor(ASM9) { private String className; @Override public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { if ((access & ACC_INTERFACE) != 0 || (access & ACC_ENUM) != 0) { Verifier.reportError("btrace.program.should.be.class"); } if ((access & ACC_PUBLIC) == 0) { Verifier.reportError("class.should.be.public", name); } if (!superName.equals(Constants.OBJECT_INTERNAL)) { Verifier.reportError("object.superclass.required", superName); } if (interfaces != null && interfaces.length > 0) { Verifier.reportError("no.interface.implementation"); } className = name; super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (className.equals(outerName)) { Verifier.reportError("no.nested.class"); } } @Override public void visitOuterClass(String s, String s1, String s2) { Verifier.reportError("no.outer.class"); } @Override public FieldVisitor visitField( int access, String name, String desc, String sig, Object dflt) { if ((access & ACC_STATIC) == 0) { Verifier.reportError("agent.no.instance.variables", name); } return super.visitField(access, name, desc, sig, dflt); } @Override public MethodVisitor visitMethod( int access, String methodName, String desc, String sig, String[] exceptions) { if ((access & ACC_SYNCHRONIZED) != 0) { Verifier.reportError( "no.synchronized.methods", TypeUtils.descriptorToSimplified(desc, className, methodName)); } if (!methodName.equals(Constants.CONSTRUCTOR)) { if ((access & ACC_STATIC) == 0) { Verifier.reportError( "no.instance.method", TypeUtils.descriptorToSimplified(desc, className, methodName)); } } if (methodName.equals(Constants.CLASS_INITIALIZER)) { return super.visitMethod(access, methodName, desc, sig, exceptions); } return new MethodVisitor( ASM9, super.visitMethod(access, methodName, desc, sig, exceptions)) { private final Map labels = new HashMap<>(); @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { if (opcode == PUTFIELD) { Verifier.reportError("no.assignment"); } if (opcode == PUTSTATIC) { if (!owner.equals(className)) { Verifier.reportError("no.assignment"); } } super.visitFieldInsn(opcode, owner, name, desc); } @Override public void visitInsn(int opcode) { switch (opcode) { case IASTORE: case LASTORE: case FASTORE: case DASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: Verifier.reportError("no.assignment"); break; case ATHROW: Verifier.reportError("no.throw"); break; case MONITORENTER: case MONITOREXIT: Verifier.reportError("no.synchronized.blocks"); break; } super.visitInsn(opcode); } @Override public void visitIntInsn(int opcode, int operand) { if (opcode == NEWARRAY) { Verifier.reportError("no.array.creation"); } super.visitIntInsn(opcode, operand); } @Override public void visitJumpInsn(int opcode, Label label) { if (labels.get(label) != null) { Verifier.reportError("no.loops"); } super.visitJumpInsn(opcode, label); } @Override public void visitMethodInsn( int opcode, String owner, String name, String desc, boolean itfc) { switch (opcode) { case INVOKEVIRTUAL: if (MethodVerifier.isPrimitiveWrapper(owner) && MethodVerifier.isUnboxMethod(name)) { // allow primitive type unbox methods. // These calls are generated by javac for auto-unboxing ` // and can't be caught by source AST analyzer as well. } else if (owner.equals(Constants.STRING_BUILDER_INTERNAL)) { // allow string concatenation via StringBuilder } else if (owner.equals(Constants.THREAD_LOCAL_INTERNAL)) { // allow ThreadLocal methods } else if (owner.equals(Constants.BTRACERTACCESS_INTERNAL)) { // allow BTraceRuntimeAccess methods } else if (owner.equals(Constants.BTRACERTBRIDGE_INTERNAL)) { // allow BTraceRuntimeImplBase methods } else { if (!delegate.isServiceType(owner)) { Verifier.reportError("no.method.calls", owner + "." + name + desc); } } break; case INVOKEINTERFACE: // allow BTraceRuntimeBridge interface methods (leave(), enter(), etc.) if (!owner.equals(Constants.BTRACERTBRIDGE_INTERNAL) && !delegate.isServiceType(owner)) { Verifier.reportError("no.method.calls", owner + "." + name + desc); } break; case INVOKESPECIAL: if (owner.equals(Constants.OBJECT_INTERNAL) && name.equals(Constants.CONSTRUCTOR)) { // allow object initializer } else if (owner.equals(Constants.STRING_BUILDER_INTERNAL)) { // allow string concatenation via StringBuilder } else if (owner.equals(Constants.THREAD_LOCAL_INTERNAL)) { // allow ThreadLocal methods } else if (delegate.isServiceType(owner)) { // allow services invocations } else { Verifier.reportError("no.method.calls", owner + "." + name + desc); } break; case INVOKESTATIC: if (!owner.startsWith("org/openjdk/btrace/") && !owner.equals(className)) { if ("valueOf".equals(name) && MethodVerifier.isPrimitiveWrapper(owner)) { // allow primitive wrapper boxing methods. // These calls are generated by javac for autoboxing // and can't be caught sourc AST analyzer as well. } else { Verifier.reportError("no.method.calls", owner + "." + name + desc); } } break; } super.visitMethodInsn(opcode, owner, name, desc, itfc); } @Override public void visitMultiANewArrayInsn(String desc, int dims) { Verifier.reportError("no.array.creation"); } @Override public void visitTypeInsn(int opcode, String desc) { if (opcode == ANEWARRAY) { Verifier.reportError("no.array.creation", desc); } if (opcode == NEW) { // allow StringBuilder creation for string concatenation if (!desc.equals(Constants.STRING_BUILDER_INTERNAL) && !delegate.isServiceType(desc)) { Verifier.reportError("no.new.object", desc); } } super.visitTypeInsn(opcode, desc); } @Override public void visitVarInsn(int opcode, int var) { if (opcode == RET) { Verifier.reportError("no.try"); } super.visitVarInsn(opcode, var); } }; } }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); } private static final class Handler { private final String name; private final String desc; public Handler(String name, String desc) { this.name = name; this.desc = desc; } @Override public int hashCode() { int hash = 7; hash = 29 * hash + Objects.hashCode(name); hash = 29 * hash + Objects.hashCode(desc); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Handler other = (Handler) obj; if (!Objects.equals(name, other.name)) { return false; } return Objects.equals(desc, other.desc); } } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeSupport.java ================================================ /* * Copyright (c) 2017,2018, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.instr; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.regex.Pattern; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.extensions.Permission; import org.openjdk.btrace.runtime.BTraceRuntimeAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.openjdk.btrace.instr.ClassFilter.isSubTypeOf; public final class BTraceProbeSupport { private static final Logger log = LoggerFactory.getLogger(BTraceProbeSupport.class); private volatile String actionPrefix = null; private static final AtomicReferenceFieldUpdater actionPrefixUpdate = AtomicReferenceFieldUpdater.newUpdater(BTraceProbeSupport.class, String.class, "actionPrefix"); private final List onMethods; private final List onProbes; private final Map serviceFields; private final EnumSet requiredPermissions; private final Object filterLock = new Object(); private volatile ClassFilter filter; private boolean trustedScript = false; private boolean classRenamed = false; private String className, origName; private volatile Class probeClass; /** * Per-probe handler {@link MethodHandle} cache. Holding it on the probe means it dies * with the probe object — no cross-probe scan on unregister, and no accidental retention * of the probe's defined {@code Class} / {@code ClassLoader} via a static map outlasting * the probe. * *

The key omits the probe name (redundant given the cache is per-probe). Sized small on * purpose: a typical probe has O(10) handlers. */ private final Map handlerCache = new ConcurrentHashMap<>(); BTraceProbeSupport() { onMethods = new ArrayList<>(); onProbes = new ArrayList<>(); serviceFields = new HashMap<>(); requiredPermissions = EnumSet.noneOf(Permission.class); } void setClassName(String name) { origName = name; String clientName = BTraceRuntimeAccess.getClientName(name); className = clientName != null ? clientName : name; classRenamed = !className.equals(name); } String getClassName(boolean internal) { return internal ? className : className.replace("/", "."); } String getOrigName() { return origName; } boolean isClassRenamed() { return classRenamed; } String translateOwner(String owner) { if (owner.equals(origName)) { return getClassName(true); } return owner; } boolean isTransforming() { return !onMethods.isEmpty(); } Collection getApplicableHandlers(BTraceClassReader cr) { Collection applicables = new ArrayList<>(onMethods.size()); String targetName = cr.getJavaClassName(); outer: for (OnMethod om : onMethods) { String probeClass = om.getClazz(); if (probeClass == null || probeClass.isEmpty()) continue; if (probeClass.equals(targetName)) { applicables.add(om); continue; } // Check regex match if (om.isClassRegexMatcher() && !om.isClassAnnotationMatcher()) { Pattern p = om.getClassPattern(); if (p != null && p.matcher(targetName).matches()) { applicables.add(om); continue; } } if (om.isClassAnnotationMatcher()) { Collection annoTypes = cr.getAnnotationTypes(); if (om.isClassRegexMatcher()) { Pattern p = om.getClassPattern(); if (p != null) { for (String annoType : annoTypes) { if (p.matcher(annoType).matches()) { applicables.add(om); continue outer; } } } } else { if (annoTypes.contains(probeClass)) { applicables.add(om); continue; } } } // And, finally, check the class hierarchy if (om.isSubtypeMatcher()) { // internal name of super type. if (isSubTypeOf(cr.getClassName(), cr.getClassLoader(), probeClass)) { applicables.add(om); } } } return applicables; } Collection getOnMethods() { return Collections.unmodifiableCollection(onMethods); } Collection getOnProbes() { return Collections.unmodifiableCollection(onProbes); } Iterable onmethods() { return () -> Collections.unmodifiableCollection(onMethods).iterator(); } Iterable onprobes() { return () -> onProbes.iterator(); } Map serviceFields() { return Collections.unmodifiableMap(serviceFields); } boolean isServiceType(String typeName) { // First check if it's a direct service type (e.g., MetricsService) if (serviceFields.containsValue(typeName)) { return true; } // Check if it's in a sub-package of any service type's package // This allows return types from service methods (e.g., HistogramMetric, HistogramSnapshot) // without hardcoding specific packages - we infer allowed packages from injected services for (String serviceType : serviceFields.values()) { // Extract package by removing the class name (everything after the last '/') int lastSlash = serviceType.lastIndexOf('/'); if (lastSlash > 0) { String servicePackage = serviceType.substring(0, lastSlash); // Allow classes in the same package or sub-packages of the service // This makes the verifier cooperate with the extension system automatically if (typeName.startsWith(servicePackage + "/")) { return true; } } } return false; } boolean isFieldInjected(String name) { return serviceFields.containsKey(name); } void addOnMethod(OnMethod om) { onMethods.add(om); } void addOnProbe(OnProbe op) { onProbes.add(op); } void addServiceField(String fldName, String svcType) { serviceFields.put(fldName, svcType); } void addRequiredPermission(Permission permission) { requiredPermissions.add(permission); } Set getRequiredPermissions() { return Collections.unmodifiableSet(requiredPermissions); } boolean willInstrument(Class clz) { return getClassFilter().isCandidate(clz); } private ClassFilter getClassFilter() { synchronized (filterLock) { if (filter == null) { filter = new ClassFilter(onmethods()); } return filter; } } void setTrusted() { trustedScript = true; } boolean isTrusted() { return trustedScript; } Class getProbeClass() { return probeClass; } void clearProbeClass() { this.probeClass = null; // Drop cached MethodHandles — they resolve into the now-released probe Class and would // otherwise keep it (and its ClassLoader) reachable across a detach. handlerCache.clear(); } MethodHandle getCachedHandler(String handlerName, MethodType type) { return handlerCache.get(new HandlerSubKey(handlerName, type)); } void cacheHandler(String handlerName, MethodType type, MethodHandle mh) { handlerCache.put(new HandlerSubKey(handlerName, type), mh); } /** * Cache key for the per-probe handler cache. The probe component is implicit (the map * lives on the probe) so we only carry {@code (handlerName, type)}. Hash is precomputed * because the key is recomputed on every resolve. */ private static final class HandlerSubKey { final String handler; final MethodType type; private final int hash; HandlerSubKey(String handler, MethodType type) { this.handler = handler; this.type = type; this.hash = handler.hashCode() * 31 + type.hashCode(); } @Override public int hashCode() { return hash; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof HandlerSubKey)) return false; HandlerSubKey k = (HandlerSubKey) o; return hash == k.hash && handler.equals(k.handler) && type.equals(k.type); } } Class defineClass(BTraceRuntime.Impl rt, byte[] code) { // This extra BTraceRuntime.enter is needed to // check whether we have already entered before. boolean enteredHere = BTraceRuntime.enter(); try { // The trace class static initializer needs to be run // without BTraceRuntime.enter(). Please look at the // static initializer code of trace class. BTraceRuntime.leave(); if (log.isDebugEnabled()) { log.debug("about to defineClass {}", getClassName(false)); } Class clz = rt.defineClass(code); if (log.isDebugEnabled()) { log.debug("defineClass succeeded for {}", getClassName(false)); } this.probeClass = clz; return clz; } finally { // leave BTraceRuntime enter state as it was before // we started executing this method. if (!enteredHere) BTraceRuntime.enter(); } } void applyArgs(ArgsMap argsMap) { for (OnMethod om : onMethods) { om.applyArgs(argsMap); } } String getActionPrefix() { return actionPrefixUpdate.updateAndGet(this, prefix -> { if (prefix == null) { prefix = Constants.BTRACE_METHOD_PREFIX + getClassName(true).replace('/', '$') + "$"; } return prefix; }); } @Override public String toString() { return "BTraceProbeSupport{" + "onMethods=" + onMethods + ", onProbes=" + onProbes + ", trustedScript=" + trustedScript + ", serviceFields=" + serviceFields + '}'; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceTransformer.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.DebugSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; /** * The single entry point for class transformation. * *

When a class is to be transformed all the registered {@linkplain BTraceProbe} instances are * asked for the appropriate instrumentation. When there are no registered probes or none of the * registered probes is able to instrument the class it will not be transformed. * * @author Jaroslav Bachorik * @since 1.3.5 */ @SuppressWarnings("RedundantThrows") public final class BTraceTransformer implements ClassFileTransformer { private static final Logger log = LoggerFactory.getLogger(BTraceTransformer.class); private final DebugSupport debug; private final ReentrantReadWriteLock setupLock = new ReentrantReadWriteLock(); private final Collection probes = new ArrayList<>(3); private final Filter filter = new Filter(); public BTraceTransformer(DebugSupport d) { debug = d; } /* * Certain classes like java.lang.ThreadLocal and it's * inner classes, java.lang.Object cannot be safely * instrumented with BTrace. This is because BTrace uses * ThreadLocal class to check recursive entries due to * BTrace's own functions. But this leads to infinite recursions * if BTrace instruments java.lang.ThreadLocal for example. * For now, we avoid such classes till we find a solution. */ private static boolean isSensitiveClass(String name) { return ClassFilter.isSensitiveClass(name); } // JDK lambda wrapper names from LambdaMetafactory: // JDK 8: $$Lambda$ (e.g. Main$$Lambda$36) // JDK 11+: $$Lambda$/0x (hidden-class suffix) // Both forms are recognised by the "$$Lambda$" infix followed by digits. static boolean isSyntheticLambda(String internalName) { if (internalName == null) return false; int idx = internalName.indexOf("$$Lambda$"); if (idx < 0) return false; int digitStart = idx + "$$Lambda$".length(); if (digitStart >= internalName.length()) return false; // Next char must be a digit (the Lambda serial number) return Character.isDigit(internalName.charAt(digitStart)); } public void register(BTraceProbe p) { try { setupLock.writeLock().lock(); probes.add(p); for (OnMethod om : p.onmethods()) { filter.add(om); } } finally { setupLock.writeLock().unlock(); } } public final void unregister(BTraceProbe p) { try { setupLock.writeLock().lock(); probes.remove(p); for (OnMethod om : p.onmethods()) { filter.remove(om); } } finally { setupLock.writeLock().unlock(); } } @Override public byte[] transform( ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { setupLock.readLock().lock(); // Skip JVM-synthesized classes that have no binary name (JDK 8 // Unsafe.defineAnonymousClass host-anonymous classes, JDK 15+ hidden // classes). These are never a user-intended tracing target: a // @OnMethod(clazz="") matcher targets classes the user // authored, not the VM's lambda/LambdaForm scaffolding. Instrumenting // them also re-enters the invokedynamic machinery that the probe // dispatch itself uses, leading to unbounded recursion on JDK 8. if (className == null) { return null; } // A special case for patching the Indy linking in order to be able to safely skip // BTrace probes while linking is still in progress. if (className.equals("java/lang/invoke/MethodHandleNatives")) { byte[] transformed = null; try { debug.dumpClass(className.replace('.', '/') + "_orig", classfileBuffer); transformed = LinkerInstrumentor.addGuard(classfileBuffer); debug.dumpClass(className.replace('.', '/'), transformed); } catch (Throwable t) { log.debug("Failed to instrument indy linking", t); } return transformed; } // Skip JVM-synthesized classes: reflection accessors (sun/reflect/Generated*, // jdk/internal/reflect/Generated*) and lambda wrappers (LambdaMetafactory names // them "$$Lambda$N" on JDK 8 and "$$Lambda$N/0x" on JDK 11+). // Instrumenting these serves no tracing purpose — they are 1:1 trampolines to a // target Method or a captured functional method the user can trace directly — // and it recursively re-enters the invokedynamic/LambdaMetafactory machinery // that the probe dispatch path relies on. if (className.startsWith("sun/reflect/Generated") || className.startsWith("jdk/internal/reflect/Generated") || isSyntheticLambda(className)) { return null; } if (probes.isEmpty()) return null; if ((loader == null || loader.equals(ClassLoader.getSystemClassLoader())) && isSensitiveClass(className)) { if (log.isDebugEnabled()) { log.debug("skipping transform for BTrace class {}", className); // NOI18N } return null; } if (filter.matchClass(className) == Filter.Result.FALSE) return null; boolean entered = BTraceRuntime.enter(); try { if (debug.isDumpClasses()) { debug.dumpClass(className.replace('.', '/') + "_orig", classfileBuffer); } BTraceClassReader cr = InstrumentUtils.newClassReader(loader, classfileBuffer); BTraceClassWriter cw = InstrumentUtils.newClassWriter(cr); for (BTraceProbe p : probes) { p.notifyTransform(className); cw.addInstrumentor(p, loader); } byte[] transformed = cw.instrument(); if (transformed == null) { // no instrumentation necessary if (log.isDebugEnabled()) { log.debug("skipping class {}", cr.getJavaClassName()); } return classfileBuffer; } else { if (log.isDebugEnabled()) { log.debug("transformed class {}", cr.getJavaClassName()); } // Optional: verify transformed class via ASM in tests. if (Boolean.getBoolean("btrace.verify.transformed") && !Boolean.TRUE.equals(VerifyGuard.IN_PROGRESS.get())) { boolean allow; String filter = System.getProperty("btrace.verify.filter"); if (filter != null && !filter.isEmpty()) { allow = className.matches(filter); } else { allow = isAppClass(className); } if (allow) { try { VerifyGuard.IN_PROGRESS.set(Boolean.TRUE); java.io.StringWriter sw = new java.io.StringWriter(); org.objectweb.asm.util.CheckClassAdapter.verify( new org.objectweb.asm.ClassReader(transformed), false, new java.io.PrintWriter(sw)); String report = sw.toString(); if (!report.isEmpty()) { log.warn("ASM verification issues for {}:\n{}", className, report); } else if (log.isDebugEnabled()) { log.debug("ASM verification OK for {}", className); } } catch (Throwable t) { // Don't break transformation on verifier diagnostics log.warn("ASM verification failed for {}: {}", className, t.toString()); } finally { VerifyGuard.IN_PROGRESS.remove(); } } } if (debug.isDumpClasses()) { debug.dumpClass(className.replace('.', '/'), transformed); } } return transformed; } catch (Throwable th) { log.warn("Failed to transform class {}", className, th); throw th; } finally { if (entered) { BTraceRuntime.leave(); } } } finally { setupLock.readLock().unlock(); } } // Helper guard and app-class predicate for verifier static final class VerifyGuard { static final ThreadLocal IN_PROGRESS = ThreadLocal.withInitial(() -> Boolean.FALSE); } private static boolean isAppClass(String internalName) { if (internalName == null) return false; return !(internalName.startsWith("java/") || internalName.startsWith("javax/") || internalName.startsWith("jdk/") || internalName.startsWith("sun/") || internalName.startsWith("com/sun/") || internalName.startsWith("org/ietf/") || internalName.startsWith("org/omg/") || internalName.startsWith("org/w3c/") || internalName.startsWith("org/xml/") || internalName.startsWith("org/openjdk/btrace/")); } static class Filter { private final Map nameMap = new HashMap<>(); private final Map nameRegexMap = new HashMap<>(); private final Map patternCache = new ConcurrentHashMap<>(); private boolean isFast = true; private boolean isRegex = false; @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") private static void addToMap(Map map, K name) { synchronized (map) { map.merge(name, 1, Integer::sum); } } @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") private static void removeFromMap(Map map, K name) { synchronized (map) { Integer i = map.get(name); if (i == null) { return; } int freq = i - 1; if (freq == 0) { map.remove(name); } } } void add(OnMethod om) { if (om.isSubtypeMatcher() || om.isClassAnnotationMatcher()) { isFast = false; } else { if (om.isClassRegexMatcher()) { isRegex = true; String name = om.getClazz().replace("\\.", "/"); Pattern pattern = patternCache.computeIfAbsent(name, Pattern::compile); addToMap(nameRegexMap, pattern); } else { String name = om.getClazz().replace('.', '/'); addToMap(nameMap, name); } } } void remove(OnMethod om) { String name = om.getClazz().replace('.', '/'); if (!(om.isSubtypeMatcher() || om.isClassAnnotationMatcher())) { if (om.isClassRegexMatcher()) { Pattern pattern = patternCache.get(name); if (pattern != null) { removeFromMap(nameRegexMap, pattern); } } else { removeFromMap(nameMap, name); } } } public Result matchClass(String className) { if (isFast) { synchronized (nameMap) { if (nameMap.containsKey(className)) { return Result.TRUE; } } if (isRegex) { synchronized (nameRegexMap) { for (Pattern p : nameRegexMap.keySet()) { if (p.matcher(className).matches()) { return Result.TRUE; } } } } return Result.FALSE; } return Result.MAYBE; } enum Result { TRUE, FALSE, MAYBE } } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/BailoutException.java ================================================ package org.openjdk.btrace.instr; /** * Dummy, non-stack-collecting runtime exception. It is used for execution control in ClassReader * instances in order to avoid processing the complete class file when the relevant info is * available right at the beginning of parsing. */ final class BailoutException extends RuntimeException { /** Shared instance to optimize the cost of throwing */ static final BailoutException INSTANCE = new BailoutException(); private BailoutException() {} @Override public synchronized Throwable fillInStackTrace() { // we don't need the stack here return this; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/CallGraph.java ================================================ /* * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; /** * This class allows building an arbitrary graph caller-callee relationship * * @author Jaroslav Bachorik */ public final class CallGraph { private static final Pattern MID_SPLIT_PTN = Pattern.compile("::"); private final Map nodes = new HashMap<>(); // O(1) lookup index private final Set startingNodes = new HashSet<>(); public static String methodId(String name, String desc) { return name + "::" + desc; } public static String[] method(String methodId) { if (methodId.contains("::")) { return MID_SPLIT_PTN.split(methodId); } return new String[0]; } public void addEdge(String fromId, String toId) { // O(1) lookup instead of O(n) Node fromNode = nodes.computeIfAbsent(fromId, Node::new); Node toNode = nodes.computeIfAbsent(toId, Node::new); fromNode.addEdge(toNode); } public void addStarting(String methodId) { // O(1) lookup Node n = nodes.computeIfAbsent(methodId, Node::new); startingNodes.add(n); } public boolean hasCycle() { Set looped = findCycles(); if (looped.isEmpty()) { return false; } Set checkingSet = new HashSet<>(looped); checkingSet.retainAll(startingNodes); if (!checkingSet.isEmpty()) { // a starting node is part of the loop return true; } Deque processingQueue = new ArrayDeque<>(); for (Node n : startingNodes) { processingQueue.push(n); do { Node current = processingQueue.pop(); if (looped.contains(current)) { // there is a path leading from a starting node to the detected loop return true; } for (Edge e : current.outgoing) { processingQueue.push(e.to); } } while (!processingQueue.isEmpty()); } return false; } void callees(String name, String desc, Set closure) { collectOutgoings(methodId(name, desc), closure); } void callers(String name, String desc, Set closure) { collectIncomings(methodId(name, desc), closure); } private void collectOutgoings(String methodId, Set closure) { // O(1) lookup instead of O(n) Node n = nodes.get(methodId); if (n != null) { for (Edge e : n.outgoing) { if (!closure.contains(e.to.id)) { closure.add(e.to.id); collectOutgoings(e.to.id, closure); } } } } private void collectIncomings(String methodId, Set closure) { // O(1) lookup instead of O(n) Node n = nodes.get(methodId); if (n != null) { for (Edge e : n.incoming) { if (!closure.contains(e.from.id)) { closure.add(e.from.id); collectIncomings(e.from.id, closure); } } } } private Set findCycles() { if (nodes.size() < 2) return Collections.emptySet(); Map checkingNodes = new HashMap<>(); for (Node n : nodes.values()) { Node newN = checkingNodes.get(n.id); if (newN == null) { newN = new Node(n.id); checkingNodes.put(n.id, newN); } for (Edge e : n.incoming) { Node fromN = checkingNodes.get(e.from.id); if (fromN == null) { fromN = new Node(e.from.id); checkingNodes.put(e.from.id, fromN); } Edge ee = new Edge(fromN, newN); newN.addIncoming(ee); fromN.addOutgoing(ee); } for (Edge e : n.outgoing) { Node toN = checkingNodes.get(e.to.id); if (toN == null) { toN = new Node(e.to.id); checkingNodes.put(e.to.id, toN); } Edge ee = new Edge(newN, toN); newN.addOutgoing(ee); toN.addIncoming(ee); } } Set sortedNodes = new HashSet<>(checkingNodes.values()); // collect all terminal nodes Deque terminalNodes = new ArrayDeque<>(); for (Node node : sortedNodes) { if ((node.incoming.isEmpty() && !startingNodes.contains(node)) || node.outgoing.isEmpty()) { terminalNodes.addLast(node); } } // remove each terminal node from the graph and if the removal creates more terminal nodes // add them all for processing while (!terminalNodes.isEmpty()) { Node n = terminalNodes.removeFirst(); sortedNodes.remove(n); for (Edge e : new HashSet<>(n.incoming)) { e.delete(); if (e.from.outgoing.isEmpty()) { terminalNodes.addLast(e.from); } } for (Edge e : new HashSet<>(n.outgoing)) { e.delete(); if (e.to.incoming.isEmpty() && !startingNodes.contains(e.to)) { terminalNodes.addLast(e.to); } } } return sortedNodes; } public static class Node { private final String id; private final Set incoming = new HashSet<>(); private final Set outgoing = new HashSet<>(); public Node(String id) { this.id = id; } public void addIncoming(Edge e) { incoming.add(e); } public void addOutgoing(Edge e) { outgoing.add(e); } public void removeIncoming(Edge e) { incoming.remove(e); } public void removeOutgoing(Edge e) { outgoing.remove(e); } public void addEdge(Node to) { Edge edge = new Edge(this, to); this.addOutgoing(edge); to.addIncoming(edge); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Node other = (Node) obj; return Objects.equals(id, other.id); } @Override public int hashCode() { int hash = 7; hash = 11 * hash + (id != null ? id.hashCode() : 0); return hash; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Node{id='").append(id).append("'}"); sb.append("\n"); sb.append("incomming:\n"); sb.append("=============================\n"); for (Edge e : incoming) { sb.append(e.from.id).append("\n"); } sb.append("=============================\n"); sb.append("outgoing:\n"); for (Edge e : outgoing) { sb.append(e.to.id).append("\n"); } sb.append("=============================\n"); return sb.toString(); } } public static class Edge { private final Node from; private final Node to; public Edge(Node from, Node to) { this.from = from; this.to = to; } public void delete() { from.removeOutgoing(this); to.removeIncoming(this); } @Override @SuppressWarnings("ReferenceEquality") public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Edge other = (Edge) obj; if (!Objects.equals(from, other.from)) { return false; } return Objects.equals(to, other.to); } @Override public int hashCode() { int hash = 5; hash = 37 * hash + (from != null ? from.hashCode() : 0); hash = 37 * hash + (to != null ? to.hashCode() : 0); return hash; } } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/CatchInstrumentor.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import java.util.HashMap; import java.util.Map; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; /** * This visitor helps in inserting code whenever an exception is caught or finally block is reached. * The code to insert on exception catch or finally may be decided by derived class. By default, * this class inserts code to print a message. * * @author A. Sundararajan */ public class CatchInstrumentor extends MethodInstrumentor { private final Map handlers = new HashMap<>(); public CatchInstrumentor( ClassLoader cl, MethodVisitor mv, MethodInstrumentorHelper mHelper, String parentClz, String superClz, int access, String name, String desc) { super(cl, mv, mHelper, parentClz, superClz, access, name, desc); } @Override public void visitLabel(Label label) { super.visitLabel(label); String catchType = handlers.get(label); if (catchType != null) { insertFrameReplaceStack(label, Type.getObjectType(catchType)); onCatch(catchType); } } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { if (type != null) { handlers.put(handler, type); } super.visitTryCatchBlock(start, end, handler, type); } protected void onCatch(String type) { asm.println("catching " + type); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassCache.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.util.Map; import java.util.Objects; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentMap; import org.jctools.maps.NonBlockingHashMap; import org.jctools.maps.NonBlockingIdentityHashMap; import org.openjdk.btrace.instr.ClassInfo.ClassName; /** * A simple class cache holding {@linkplain ClassInfo} instances and being searchable either by * {@linkplain Class} or a tuple of {@code (className, classLoader)} * * @author Jaroslav Bachorik */ public final class ClassCache { private static final class CacheKey { public final String name; public final int id; private final int hashCode; public CacheKey(String name, int id) { this.name = name; this.id = id; this.hashCode = Objects.hash(name, id); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CacheKey cacheKey = (CacheKey) o; return id == cacheKey.id && name.equals(cacheKey.name); } @Override public int hashCode() { return hashCode; } } private static final class ClassLoaderReference extends PhantomReference { final CacheKey key; public ClassLoaderReference(ClassLoader referent, ReferenceQueue q) { super(referent, q); this.key = getCacheKey(referent); } } private final Map loaderRefs = new NonBlockingIdentityHashMap<>(); private final ReferenceQueue cleanupQueue = new ReferenceQueue<>(); private final ConcurrentMap> cacheMap = new NonBlockingHashMap<>(); private final ConcurrentMap bootstrapInfos = new NonBlockingHashMap<>(500); private final Timer cleanupTimer = new Timer(true); public static ClassCache getInstance() { return Singleton.INSTANCE; } ClassCache(long cleanupPeriod) { cleanupTimer.schedule( new TimerTask() { @Override public void run() { ClassLoaderReference ref = null; while ((ref = (ClassLoaderReference) cleanupQueue.poll()) != null) { cacheMap.remove(ref.key); loaderRefs.remove(ref); } } }, cleanupPeriod, cleanupPeriod); } public ClassInfo get(Class clz) { return get(clz.getClassLoader(), clz.getName()); } /** * Returns a cached {@linkplain ClassInfo} value. If the corresponding value has not been cached * yet then it is created and put into the cache. * * @param cl The associated {@linkplain ClassLoader} * @param className The Java class name or internal class name */ public ClassInfo get(ClassLoader cl, String className) { return get(cl, new ClassName(className)); } ClassInfo get(ClassLoader cl, ClassName className) { ConcurrentMap infos = getInfos(cl); return infos.computeIfAbsent(className, k -> new ClassInfo(ClassCache.this, cl, k)); } ConcurrentMap getInfos(ClassLoader cl) { if (cl == null) { return bootstrapInfos; } boolean[] rslt = new boolean[] {false}; ConcurrentMap infos = cacheMap.computeIfAbsent( getCacheKey(cl), k -> { rslt[0] = true; return new NonBlockingHashMap<>(500); }); if (rslt[0]) { ClassLoaderReference ref = new ClassLoaderReference(cl, cleanupQueue); loaderRefs.put(ref, ref); } return infos; } private static CacheKey getCacheKey(ClassLoader cl) { return new CacheKey(cl.getClass().getName(), System.identityHashCode(cl)); } int getSize() { return cacheMap.size(); } private static final class Singleton { private static final ClassCache INSTANCE = new ClassCache(5000); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassFilter.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.openjdk.btrace.core.PrefixMap; import org.openjdk.btrace.core.annotations.BTrace; import java.lang.annotation.Annotation; import java.lang.ref.Reference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * This class checks whether a given target class matches at least one probe specified in a BTrace * class. * * @author A. Sundararajan */ public class ClassFilter { private static final Class REFERENCE_CLASS = Reference.class; private static final PrefixMap SENSITIVE_CLASSES = new PrefixMap(); // Method-level sensitive filter: internalClassName -> set of "name+desc" signatures private static final Map> SENSITIVE_METHODS = new HashMap<>(); static { ClassReader.class.getClassLoader(); AnnotationVisitor.class.getClassLoader(); FieldVisitor.class.getClassLoader(); MethodVisitor.class.getClassLoader(); Attribute.class.getClassLoader(); SENSITIVE_CLASSES.add("java/lang/Integer"); SENSITIVE_CLASSES.add("java/lang/Number"); SENSITIVE_CLASSES.add("java/lang/Object"); SENSITIVE_CLASSES.add("java/lang/String"); SENSITIVE_CLASSES.add("java/lang/StringUTF16"); SENSITIVE_CLASSES.add("java/lang/ThreadLocal"); SENSITIVE_CLASSES.add("java/lang/ThreadLocal$ThreadLocalMap"); SENSITIVE_CLASSES.add("java/lang/WeakPairMap"); SENSITIVE_CLASSES.add("java/lang/WeakPairMap$Pair$Weak"); SENSITIVE_CLASSES.add("java/lang/instrument/"); SENSITIVE_CLASSES.add("java/lang/invoke/"); SENSITIVE_CLASSES.add("java/lang/ref/"); SENSITIVE_CLASSES.add("java/util/concurrent/locks/LockSupport"); SENSITIVE_CLASSES.add("java/util/concurrent/locks/AbstractQueuedSynchronizer"); SENSITIVE_CLASSES.add("java/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode"); SENSITIVE_CLASSES.add("java/util/concurrent/locks/AbstractQueuedSynchronizer$Node"); SENSITIVE_CLASSES.add("java/util/concurrent/locks/AbstractOwnableSynchronizer"); SENSITIVE_CLASSES.add("java/util/concurrent/locks/ReentrantLock"); SENSITIVE_CLASSES.add("java/util/concurrent/ConcurrentHashMap"); SENSITIVE_CLASSES.add("jdk/internal/"); SENSITIVE_CLASSES.add("sun/invoke/"); SENSITIVE_CLASSES.add("sun/reflect/"); SENSITIVE_CLASSES.add("org/openjdk/btrace/"); // Method-level exclusions for Thread: ThreadLocal accessor/mutator methods create infinite // recursion on JDK 25+ where ThreadLocal.createMap calls Thread.setThreadLocals. addSensitiveMethod("java/lang/Thread", "threadLocals"); addSensitiveMethod("java/lang/Thread", "setThreadLocals"); addSensitiveMethod("java/lang/Thread", "terminatingThreadLocals"); addSensitiveMethod("java/lang/Thread", "setTerminatingThreadLocals"); } private final List onMethods; private Set sourceClasses; private Pattern[] sourceClassPatterns; private String[] annotationClasses; private Pattern[] annotationClassPatterns; // +foo type class pattern in any @OnMethod. private String[] superTypes; // same as above but stored in internal name form ('/' instead of '.') private String[] superTypesInternal; public ClassFilter(Iterable onMethods) { this.onMethods = new ArrayList<>(); for (OnMethod om : onMethods) { this.onMethods.add(om); } init(); } /* * return whether given Class is subtype of given type name * Note that we can not use Class.isAssignableFrom because the other * type is specified by just name and not by Class object. */ public static boolean isSubTypeOf(Class clazz, String typeName) { if (clazz == null) { return false; } else if (clazz.getName().equals(typeName)) { return true; } else { for (Class iface : clazz.getInterfaces()) { if (isSubTypeOf(iface, typeName)) { return true; } } return isSubTypeOf(clazz.getSuperclass(), typeName); } } /** * Return whether given Class typeA is subtype of any of the given type names. * * @param typeA the type to check * @param loader the classloader for loading the type (my be null) * @param types any requested supertypes */ public static boolean isSubTypeOf(String typeA, ClassLoader loader, String... types) { if (typeA == null || typeA.equals(Constants.OBJECT_INTERNAL)) { return false; } if (types.length == 0) { return false; } boolean internal = types[0].contains("/"); loader = (loader != null ? loader : ClassLoader.getSystemClassLoader()); if (internal) { typeA = typeA.replace('.', '/'); } else { typeA = typeA.replace('/', '.'); } Set typeSet = new HashSet<>(Arrays.asList(types)); if (typeSet.contains(typeA)) { return true; } ClassInfo ci = ClassCache.getInstance().get(loader, typeA); Collection sTypesInfo = ci.getSupertypes(false); if (sTypesInfo != null) { Collection sTypes = new ArrayList<>(sTypesInfo.size()); for (ClassInfo sCi : sTypesInfo) { sTypes.add(internal ? sCi.getClassName() : sCi.getJavaClassName()); } sTypes.retainAll(typeSet); return !sTypes.isEmpty(); } else { return false; } } /* * Certain classes like java.lang.ThreadLocal and it's * inner classes, java.lang.Object cannot be safely * instrumented with BTrace. This is because BTrace uses * ThreadLocal class to check recursive entries due to * BTrace's own functions. But this leads to infinite recursions * if BTrace instruments java.lang.ThreadLocal for example. * For now, we avoid such classes till we find a solution. */ public static boolean isSensitiveClass(String name) { return SENSITIVE_CLASSES.contains(name); } public static boolean isSensitiveMethod(String owner, String name, String desc) { Set methods = SENSITIVE_METHODS.get(owner); return methods != null && !methods.isEmpty() && methods.contains(name); } private static void addSensitiveMethod(String owner, String name) { SENSITIVE_METHODS.computeIfAbsent(owner, k -> new HashSet<>()).add(name); } public boolean isCandidate(Class target) { if (target.isInterface() || target.isPrimitive() || target.isArray()) { return false; } if (REFERENCE_CLASS.equals(target)) { // instrumenting the java.lang.ref.Reference class will lead // to StackOverflowError in java.lang.ThreadLocal.get() return false; } try { // ignore classes annotated with @BTrace - // We don't want to instrument tracing classes! if (target.getAnnotation(BTrace.class) != null) { return false; } } catch (Throwable t) { // thrown from java.lang.Class.initAnnotationsIfNecessary() // seems to be a case when trying to access non-existing annotations // on a superclass // * messed up situation - ignore the class * return false; } String className = target.getName(); if (isNameMatching(className)) { return true; } for (Pattern pat : sourceClassPatterns) { if (pat.matcher(className).matches()) { return true; } } for (String st : superTypes) { if (isSubTypeOf(target, st)) { return true; } } Annotation[] annotations = target.getAnnotations(); String[] annoTypes = new String[annotations.length]; for (int i = 0; i < annotations.length; i++) { annoTypes[i] = annotations[i].annotationType().getName(); } for (String name : annotationClasses) { for (String annoType : annoTypes) { if (name.equals(annoType)) { return true; } } } for (Pattern pat : annotationClassPatterns) { for (String annoType : annoTypes) { if (pat.matcher(annoType).matches()) { return true; } } } return false; } public boolean isNameMatching(String clzName) { if (sourceClasses.contains(clzName)) { return true; } for (Pattern pat : sourceClassPatterns) { if (pat.matcher(clzName).matches()) { return true; } } return false; } private void init() { List strSrcList = new ArrayList<>(); List patSrcList = new ArrayList<>(); List superTypesList = new ArrayList<>(); List superTypesInternalList = new ArrayList<>(); List strAnoList = new ArrayList<>(); List patAnoList = new ArrayList<>(); for (OnMethod om : onMethods) { String className = om.getClazz(); if (className.length() == 0) { continue; } if (om.isClassRegexMatcher()) { try { Pattern p = Pattern.compile(className); if (om.isClassAnnotationMatcher()) { patAnoList.add(p); } else { patSrcList.add(p); } } catch (PatternSyntaxException pse) { System.err.println( "btrace ERROR: invalid regex pattern - " + className.substring(1, className.length() - 1)); } } else if (om.isClassAnnotationMatcher()) { strAnoList.add(className); } else if (om.isSubtypeMatcher()) { superTypesList.add(className); superTypesInternalList.add(className.replace('.', '/')); } else { strSrcList.add(className); } } sourceClasses = new HashSet<>(strSrcList.size()); sourceClasses.addAll(strSrcList); sourceClassPatterns = new Pattern[patSrcList.size()]; patSrcList.toArray(sourceClassPatterns); superTypes = new String[superTypesList.size()]; superTypesList.toArray(superTypes); superTypesInternal = new String[superTypesInternalList.size()]; superTypesInternalList.toArray(superTypesInternal); annotationClasses = new String[strAnoList.size()]; strAnoList.toArray(annotationClasses); annotationClassPatterns = new Pattern[patAnoList.size()]; patAnoList.toArray(annotationClassPatterns); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassInfo.java ================================================ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import org.openjdk.btrace.core.BTraceRuntime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; /** * Arbitrary class info type allowing access to supertype information also for not-already-loaded * classes. * * @author Jaroslav Bachorik */ public final class ClassInfo { private static final Logger log = LoggerFactory.getLogger(ClassInfo.class); private static final ClassLoader SYS_CL = ClassLoader.getSystemClassLoader(); private static volatile MethodHandle BSTRP_CHECK_MTD; private final String cLoaderId; private final ClassName classId; // @ThreadSafe private final Collection supertypes = new ArrayList<>(); private final ClassCache cache; private boolean isInterface = false; private boolean isAvailable = false; ClassInfo(ClassCache cache, Class clz) { this.cache = cache; ClassLoader cl = clz.getClassLoader(); cLoaderId = (cl != null ? cl.toString() : ""); classId = new ClassName(clz.getName()); Class supr = clz.getSuperclass(); if (supr != null) { supertypes.add(cache.get(supr)); } for (Class itfc : clz.getInterfaces()) { if (itfc != null) { supertypes.add(cache.get(itfc)); } } isInterface = clz.isInterface(); isAvailable = true; } ClassInfo(ClassCache cache, ClassLoader cl, ClassName cName) { this.cache = cache; cLoaderId = (cl != null ? cl.toString() : ""); classId = cName; loadExternalClass(cl, cName); } private static ClassLoader inferClassLoader(ClassLoader initiating, ClassName className) { if (className == null) { return initiating; } String jClassName = className.getJavaClassName().toString(); if (initiating == null || isBootstrap(jClassName)) { return null; } else { String rsrcName = className.getResourcePath(); ClassLoader cl = initiating; ClassLoader prev = initiating; while (cl != null) { try { if (cl.getResource(rsrcName) == null) { return prev; } } catch (Throwable t) { // some containers can impose additional restrictions on loading resources and error on // unexpected state log.warn("Failed to get resource {}", rsrcName, t); } prev = cl; cl = cl.getParent(); } return initiating; } } // package private only for testing purposes static boolean isBootstrap(String className) { return BTraceRuntime.isBootstrapClass(className); } /** * Retrieves supertypes (including interfaces) * * @param onlyDirect only immediate supertype and implemented interfaces * @return supertypes (including interfaces) */ public Collection getSupertypes(boolean onlyDirect) { if (onlyDirect) { return supertypes; } Set supers = new LinkedHashSet<>(supertypes); for (ClassInfo ci : supertypes) { supers.addAll(ci.getSupertypes(onlyDirect)); } return supers; } /** * Associated class loader string representation as returned by {@code cl.toString()} or {@code * ""} * * @return associated class loader id */ public String getLoaderId() { return cLoaderId; } /** * Class ID = internal class name * * @return internal class name */ public String getClassName() { return classId.getInternalClassName().toString(); } public String getJavaClassName() { return classId.getJavaClassName().toString(); } public boolean isInterface() { return isInterface; } public boolean isAvailable() { return isAvailable; } // not thread safe - must be called only from the constructor private void loadExternalClass(ClassLoader cl, ClassName className) { String resourcePath = className.getResourcePath(); try { InputStream typeIs = cl == null ? SYS_CL.getResourceAsStream(resourcePath) : cl.getResourceAsStream(resourcePath); if (typeIs != null) { try { BTraceClassReader cr = new BTraceClassReader(cl, typeIs); isInterface = cr.isInterface(); String[] info = cr.readClassSupers(); String superName = info[0]; if (superName != null) { ClassName superClassName = new ClassName(superName); supertypes.add(cache.get(inferClassLoader(cl, superClassName), superClassName)); } if (info.length > 1) { for (int i = 1; i < info.length; i++) { String ifc = info[i]; if (ifc != null) { ClassName ifcClassName = new ClassName(ifc); supertypes.add(cache.get(inferClassLoader(cl, ifcClassName), ifcClassName)); } } } isAvailable = true; } catch (IllegalArgumentException | IOException e) { log.warn("Unable to load class: {}", className, e); } } } catch (Throwable t) { // some containers can impose additional restrictions on classloaders throwing exceptions when // not in expected state log.warn("Failed to load class {}", className, t); } } @Override public int hashCode() { int hash = 5; hash = 37 * hash + Objects.hashCode(cLoaderId); hash = 37 * hash + Objects.hashCode(classId); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ClassInfo other = (ClassInfo) obj; if (!Objects.equals(cLoaderId, other.cLoaderId)) { return false; } return Objects.equals(classId, other.classId); } @Override public String toString() { return "ClassInfo{" + "cLoaderId=" + cLoaderId + ", classId=" + classId + ", supertypes=" + supertypes + '}'; } private abstract static class BaseClassName implements CharSequence { protected final CharSequence wrapped; private String str = null; protected BaseClassName(CharSequence wrapped) { this.wrapped = wrapped; } @Override public int length() { return wrapped.length(); } @Override public CharSequence subSequence(int start, int end) { throw new UnsupportedOperationException(); } @Override public String toString() { if (str == null) { char[] val = new char[wrapped.length()]; for (int i = 0; i < wrapped.length(); i++) { val[i] = charAt(i); } str = new String(val); } return str; } } private static final class JavaClassName extends BaseClassName { public JavaClassName(CharSequence wrapped) { super(wrapped); } @Override public char charAt(int index) { char c = wrapped.charAt(index); return (c == '/' ? '.' : c); } } private static final class InternalClassName extends BaseClassName { public InternalClassName(CharSequence wrapped) { super(wrapped); } @Override public char charAt(int index) { char c = wrapped.charAt(index); return (c == '.' ? '/' : c); } } static final class ClassName { private final CharSequence cName; private final JavaClassName jcName; private final InternalClassName icName; private String rsrcName = null; public ClassName(CharSequence cName) { this.cName = cName; jcName = new JavaClassName(cName); icName = new InternalClassName(cName); } public CharSequence getJavaClassName() { return jcName; } public CharSequence getInternalClassName() { return icName; } public String getResourcePath() { if (rsrcName == null) { rsrcName = icName + ".class"; } return rsrcName; } @Override public String toString() { return String.valueOf(cName); } @Override public int hashCode() { int h = 7; int len = cName.length(); for (int i = 0; i < len; i++) { char c = cName.charAt(i); h = 31 * h + (c == '.' ? '/' : c); } return h; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ClassName other = (ClassName) obj; if (cName.length() != other.cName.length()) { return false; } for (int i = 0; i < cName.length(); i++) { char c1 = cName.charAt(i); char c2 = other.cName.charAt(i); switch (c1) { case '.': case '/': { if (c2 != '.' && c2 != '/') { return false; } break; } default: { if (c1 != c2) { return false; } } } } return true; } } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/Constants.java ================================================ /* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import java.util.regex.Pattern; import org.objectweb.asm.Type; import org.openjdk.btrace.core.ArgsMap; import org.openjdk.btrace.core.BTraceUtils; import org.openjdk.btrace.core.annotations.BTrace; import org.openjdk.btrace.core.annotations.Duration; import org.openjdk.btrace.core.annotations.Injected; import org.openjdk.btrace.core.annotations.Kind; import org.openjdk.btrace.core.annotations.Level; import org.openjdk.btrace.core.annotations.Location; import org.openjdk.btrace.core.annotations.OnError; import org.openjdk.btrace.core.annotations.OnEvent; import org.openjdk.btrace.core.annotations.OnExit; import org.openjdk.btrace.core.annotations.OnLowMemory; import org.openjdk.btrace.core.annotations.OnMethod; import org.openjdk.btrace.core.annotations.OnProbe; import org.openjdk.btrace.core.annotations.OnTimer; import org.openjdk.btrace.core.annotations.PeriodicEvent; import org.openjdk.btrace.core.annotations.ProbeClassName; import org.openjdk.btrace.core.annotations.ProbeMethodName; import org.openjdk.btrace.core.annotations.Return; import org.openjdk.btrace.core.annotations.Sampled; import org.openjdk.btrace.core.annotations.Self; import org.openjdk.btrace.core.extensions.Extension; import org.openjdk.btrace.core.annotations.TargetInstance; import org.openjdk.btrace.core.annotations.TargetMethodOrField; import org.openjdk.btrace.core.annotations.Where; import org.openjdk.btrace.core.jfr.JfrEvent; import org.openjdk.btrace.runtime.LinkingFlag; /** * Constants shared by few classes. * * @author A. Sundararajan */ public abstract class Constants { public static final String BTRACE_METHOD_PREFIX = "$btrace$"; public static final String CONSTRUCTOR = ""; public static final String CLASS_INITIALIZER = ""; public static final Type NULL_TYPE = Type.getType("L$$null"); public static final Type TOP_TYPE = Type.getType("L$$top"); public static final Type VOIDREF_TYPE = Type.getType("Ljava/lang/Void;"); public static final String OBJECT_INTERNAL = "java/lang/Object"; public static final String OBJECT_DESC = "L" + OBJECT_INTERNAL + ";"; public static final Type OBJECT_TYPE = Type.getType(OBJECT_DESC); public static final String ANYTYPE_INTERNAL = "org/openjdk/btrace/core/types/AnyType"; public static final String ANYTYPE_DESC = "L" + ANYTYPE_INTERNAL + ";"; public static final Type ANYTYPE_TYPE = Type.getType(ANYTYPE_DESC); public static final String CLASS_DESC = "Ljava/lang/Class;"; public static final Type CLASS_TYPE = Type.getType(CLASS_DESC); public static final String STRING_INTERNAL = "java/lang/String"; public static final String STRING_DESC = "L" + STRING_INTERNAL + ";"; public static final Type STRING_TYPE = Type.getType(STRING_DESC); public static final String STRING_BUILDER_INTERNAL = "java/lang/StringBuilder"; public static final String STRING_BUILDER_DESC = "L" + STRING_BUILDER_INTERNAL + ";"; public static final Type STRING_BUILDER_TYPE = Type.getType(STRING_BUILDER_DESC); public static final String VOID_DESC = "V"; public static final String BOOLEAN_DESC = "Z"; public static final String INT_DESC = "I"; public static final String THROWABLE_INTERNAL = "java/lang/Throwable"; public static final String THROWABLE_DESC = "L" + THROWABLE_INTERNAL + ";"; public static final Type THROWABLE_TYPE = Type.getType(THROWABLE_DESC); public static final String BTRACERTACCESS_INTERNAL = "org/openjdk/btrace/runtime/BTraceRuntimeAccess"; public static final String BTRACERTACCESS_DESC = "L" + BTRACERTACCESS_INTERNAL + ";"; public static final String BTRACERT_INTERNAL = "org/openjdk/btrace/core/BTraceRuntime"; public static final String BTRACERT_DESC = "L" + BTRACERT_INTERNAL + ";"; public static final String BTRACERTIMPL_INTERNAL = "org/openjdk/btrace/core/BTraceRuntime$Impl"; public static final String BTRACERTIMPL_DESC = "L" + BTRACERTIMPL_INTERNAL + ";"; public static final String BTRACERTBRIDGE_INTERNAL = "org/openjdk/btrace/core/BTraceRuntimeBridge"; public static final String BTRACERTBRIDGE_DESC = "L" + BTRACERTBRIDGE_INTERNAL + ";"; public static final Type BTRACERT_TYPE = Type.getType(BTRACERT_DESC); public static final String THREAD_LOCAL_INTERNAL = "java/lang/ThreadLocal"; public static final String THREAD_LOCAL_DESC = "L" + THREAD_LOCAL_INTERNAL + ";"; public static final Type THREAD_LOCAL_TYPE = Type.getType(ThreadLocal.class); public static final Type LINKING_FLAG_TYPE = Type.getType(LinkingFlag.class); public static final String LINKING_FLAG_INTERNAL = LINKING_FLAG_TYPE.getInternalName(); // BTrace specific stuff public static final String BTRACE_UTILS = Type.getInternalName(BTraceUtils.class); public static final String EXTENSION = Type.getInternalName(Extension.class); public static final String EXTENSION_DESC = Type.getDescriptor(Extension.class); public static final String BTRACE_DESC = Type.getDescriptor(BTrace.class); public static final String ONMETHOD_DESC = Type.getDescriptor(OnMethod.class); public static final String JFRPERIODIC_DESC = Type.getDescriptor(PeriodicEvent.class); public static final String JFREVENTFACTORY_DESC = Type.getDescriptor(JfrEvent.Factory.class); public static final String BTRACE_PROBECLASSNAME_DESC = Type.getDescriptor(ProbeClassName.class); public static final String BTRACE_PROBEMETHODNAME_DESC = Type.getDescriptor(ProbeMethodName.class); public static final String ONTIMER_DESC = Type.getDescriptor(OnTimer.class); public static final String ONEVENT_DESC = Type.getDescriptor(OnEvent.class); public static final String ONEXIT_DESC = Type.getDescriptor(OnExit.class); public static final String ONERROR_DESC = Type.getDescriptor(OnError.class); public static final String ONLOWMEMORY_DESC = Type.getDescriptor(OnLowMemory.class); public static final String SAMPLED_DESC = Type.getDescriptor(Sampled.class); public static final String SAMPLER_DESC = Type.getDescriptor(Sampled.Sampler.class); public static final String ONPROBE_DESC = Type.getDescriptor(OnProbe.class); public static final String LOCATION_DESC = Type.getDescriptor(Location.class); public static final String LEVEL_DESC = Type.getDescriptor(Level.class); public static final String WHERE_DESC = Type.getDescriptor(Where.class); public static final String KIND_DESC = Type.getDescriptor(Kind.class); public static final String INJECTED_DESC = Type.getDescriptor(Injected.class); // RequestPermission annotations removed; permissions are managed via descriptors and manifest. public static final String RETURN_DESC = Type.getDescriptor(Return.class); public static final String SELF_DESC = Type.getDescriptor(Self.class); public static final String TARGETMETHOD_DESC = Type.getDescriptor(TargetMethodOrField.class); public static final String TARGETINSTANCE_DESC = Type.getDescriptor(TargetInstance.class); public static final String DURATION_DESC = Type.getDescriptor(Duration.class); public static final String ARGSMAP_DESC = Type.getDescriptor(ArgsMap.class); // class name pattern is specified with this pattern public static final Pattern REGEX_SPECIFIER = Pattern.compile("/.+/"); public static final String JAVA_LANG_THREAD_LOCAL = Type.getInternalName(ThreadLocal.class); public static final String JAVA_LANG_THREAD_LOCAL_GET = "get"; public static final String JAVA_LANG_THREAD_LOCAL_GET_DESC = "()Ljava/lang/Object;"; public static final String JAVA_LANG_THREAD_LOCAL_SET = "set"; public static final String JAVA_LANG_THREAD_LOCAL_SET_DESC = "(Ljava/lang/Object;)V"; public static final String NUMBER_INTERNAL = "java/lang/Number"; public static final String INTEGER_BOXED_INTERNAL = "java/lang/Integer"; public static final String INTEGER_BOXED_DESC = "L" + INTEGER_BOXED_INTERNAL + ";"; public static final String SHORT_BOXED_INTERNAL = "java/lang/Short"; public static final String SHORT_BOXED_DESC = "L" + SHORT_BOXED_INTERNAL + ";"; public static final String LONG_BOXED_INTERNAL = "java/lang/Long"; public static final String LONG_BOXED_DESC = "L" + LONG_BOXED_INTERNAL + ";"; public static final String FLOAT_BOXED_INTERNAL = "java/lang/Float"; public static final String FLOAT_BOXED_DESC = "L" + FLOAT_BOXED_INTERNAL + ";"; public static final String DOUBLE_BOXED_INTERNAL = "java/lang/Double"; public static final String DOUBLE_BOXED_DESC = "L" + DOUBLE_BOXED_INTERNAL + ";"; public static final String BYTE_BOXED_INTERNAL = "java/lang/Byte"; public static final String BYTE_BOXED_DESC = "L" + BYTE_BOXED_INTERNAL + ";"; public static final String BOOLEAN_BOXED_INTERNAL = "java/lang/Boolean"; public static final String BOOLEAN_BOXED_DESC = "L" + BOOLEAN_BOXED_INTERNAL + ";"; public static final String CHARACTER_BOXED_INTERNAL = "java/lang/Character"; public static final String CHARACTER_BOXED_DESC = "L" + CHARACTER_BOXED_INTERNAL + ";"; public static final String BOX_VALUEOF = "valueOf"; public static final String BOX_BOOLEAN_DESC = "(" + BOOLEAN_DESC + ")" + BOOLEAN_BOXED_DESC; public static final String BOX_CHARACTER_DESC = "(C)Ljava/lang/Character;"; public static final String BOX_BYTE_DESC = "(B)Ljava/lang/Byte;"; public static final String BOX_SHORT_DESC = "(S)Ljava/lang/Short;"; public static final String BOX_INTEGER_DESC = "(I)Ljava/lang/Integer;"; public static final String BOX_LONG_DESC = "(J)Ljava/lang/Long;"; public static final String BOX_FLOAT_DESC = "(F)Ljava/lang/Float;"; public static final String BOX_DOUBLE_DESC = "(D)Ljava/lang/Double;"; public static final String BOOLEAN_VALUE = "booleanValue"; public static final String CHAR_VALUE = "charValue"; public static final String BYTE_VALUE = "byteValue"; public static final String SHORT_VALUE = "shortValue"; public static final String INT_VALUE = "intValue"; public static final String LONG_VALUE = "longValue"; public static final String FLOAT_VALUE = "floatValue"; public static final String DOUBLE_VALUE = "doubleValue"; public static final String BOOLEAN_VALUE_DESC = "()Z"; public static final String CHAR_VALUE_DESC = "()C"; public static final String BYTE_VALUE_DESC = "()B"; public static final String SHORT_VALUE_DESC = "()S"; public static final String INT_VALUE_DESC = "()I"; public static final String LONG_VALUE_DESC = "()J"; public static final String FLOAT_VALUE_DESC = "()F"; public static final String DOUBLE_VALUE_DESC = "()D"; public static final String EMBEDDED_BTRACE_SECTION_HEADER = "META-INF/btrace/"; public static final String BTRACE_LEVEL_FLD = "$btrace$$level"; } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/ErrorReturnInstrumentor.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import static org.objectweb.asm.Opcodes.ATHROW; import static org.openjdk.btrace.instr.Constants.THROWABLE_INTERNAL; import static org.openjdk.btrace.instr.Constants.THROWABLE_TYPE; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; /** * This visitor helps in inserting code whenever a method "returns" because of an exception (i.e., * no exception handler in the method and so it's frame poped). The code to insert on method error * return may be decided by derived class. By default, this class inserts code to print message to * say "no handler here". * * @author A. Sundararajan */ public class ErrorReturnInstrumentor extends MethodReturnInstrumentor { private final Label start = new Label(); private final Label end = new Label(); public ErrorReturnInstrumentor( ClassLoader cl, MethodVisitor mv, MethodInstrumentorHelper mHelper, String parentClz, String superClz, int access, String name, String desc) { super(cl, mv, mHelper, parentClz, superClz, access, name, desc); } @Override protected void visitMethodPrologue() { addTryCatchHandler(start, end); visitLabel(start); super.visitMethodPrologue(); } @Override public void visitMaxs(int maxStack, int maxLocals) { visitTryCatchBlock(start, end, end, THROWABLE_INTERNAL); visitLabel(end); insertFrameReplaceStack(end, THROWABLE_TYPE); onErrorReturn(); visitInsn(ATHROW); super.visitMaxs(maxStack, maxLocals); } @Override protected void onMethodEntry() {} @Override protected void onMethodReturn(int opcode) {} protected void onErrorReturn() { asm.println("error return from " + getName() + getDescriptor()); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/FieldAccessInstrumentor.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the Classpath exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.MethodVisitor; /** * This visitor helps in inserting code whenever an field access is done. The code to insert on * field access may be decided by derived class. By default, this class inserts code to print the * field access. * * @author A. Sundararajan */ public class FieldAccessInstrumentor extends MethodInstrumentor { protected boolean isStaticAccess = false; public FieldAccessInstrumentor( ClassLoader cl, MethodVisitor mv, MethodInstrumentorHelper mHelper, String parentClz, String superClz, int access, String name, String desc) { super(cl, mv, mHelper, parentClz, superClz, access, name, desc); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { boolean get; // ignore any internal BTrace fields if (name.contains("$btrace$")) { super.visitFieldInsn(opcode, owner, name, desc); return; } get = opcode == GETFIELD || opcode == GETSTATIC; isStaticAccess = (opcode == GETSTATIC || opcode == PUTSTATIC); if (get) { onBeforeGetField(opcode, owner, name, desc); } else { onBeforePutField(opcode, owner, name, desc); } super.visitFieldInsn(opcode, owner, name, desc); if (get) { onAfterGetField(opcode, owner, name, desc); } else { onAfterPutField(opcode, owner, name, desc); } } protected void onBeforeGetField(int opcode, String owner, String name, String desc) {} protected void onAfterGetField(int opcode, String owner, String name, String desc) {} protected void onBeforePutField(int opcode, String owner, String name, String desc) {} protected void onAfterPutField(int opcode, String owner, String name, String desc) {} } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/HandlerRepositoryImpl.java ================================================ package org.openjdk.btrace.instr; import org.openjdk.btrace.core.HandlerRepository; import org.openjdk.btrace.core.SharedSettings; import org.openjdk.btrace.runtime.IndyDispatcher; import org.openjdk.btrace.runtime.Interval; import org.openjdk.btrace.runtime.BTraceRuntimes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Implementation of {@link HandlerRepository} that resolves probe handler {@link MethodHandle}s * via {@link MethodHandles#publicLookup()}.findStatic() on the probe class. * *

Probe handler methods stay in the probe class (per-probe ClassLoader on JDK 8/9-14, or * a hidden class on JDK 15+). No bytecode copying is performed. * *

Caching: only successful resolutions are cached; failures are returned as * {@code null} without poisoning the cache. IndyDispatcher handles transient failure by * installing a {@code MutableCallSite} trampoline that retries on each invocation, so a * negative cache is not needed — and would in fact defeat the self-healing behaviour. */ public final class HandlerRepositoryImpl { private static final Logger log = LoggerFactory.getLogger(HandlerRepositoryImpl.class); static { // Wire up to IndyDispatcher (bootstrap CL) via reflection. // IndyDispatcher.repository is set to a method-reference calling our resolveHandler(). try { Class dispatcherClz = Class.forName("org.openjdk.btrace.runtime.IndyDispatcher"); HandlerRepository hook = HandlerRepositoryImpl::resolveHandler; dispatcherClz.getField("repository").set(null, hook); } catch (Throwable t) { log.warn("Unable to initialize BTrace IndyDispatcher support", t); } } /** Maps probe class name (internal form) → live BTraceProbe instance. */ private static final Map probeMap = new ConcurrentHashMap<>(); /** * Register a probe after its class has been defined in the JVM. Must be called before * any instrumented call site targeting this probe is invoked. If invocation arrives * first, {@link org.openjdk.btrace.runtime.IndyDispatcher} installs a self-relinking * trampoline that will pick up the probe on its next invocation. */ public static void registerProbe(BTraceProbe probe) { String probeName = probe.getClassName(true); probeMap.put(probeName, probe); } /** * Unregister a probe. Also invalidates every live {@link java.lang.invoke.MutableCallSite} * targeting this probe via {@link IndyDispatcher#invalidateProbe(String)}, swapping their * targets to a noop so in-flight and subsequent invocations do not enter probe handler * bodies after the associated {@code BTraceRuntime} state has been torn down. This is the * dispatch-level equivalent of the older "cushion" approach, which stubbed probe method * bodies via bytecode redefine on detach — both exist to keep the instrumented application * from crashing when a probe is undeployed while call sites are still live. * *

The handler {@link MethodHandle} cache is per-probe (see {@link BTraceProbe#cacheHandler}) * so nothing to scan here — the cache dies with the probe object. */ public static void unregisterProbe(BTraceProbe probe) { String probeName = probe.getClassName(true); probeMap.remove(probeName); IndyDispatcher.invalidateProbe(probeName); } /** * Apply level-based guard to a handler MethodHandle. If the handler has a level requirement * (enableAt annotation), wraps the handler in a guard that checks if the current probe level * permits execution. This allows the JIT to optimize based on the level and avoids deopt * when levels change. * * @param handler the resolved handler MethodHandle * @param probe the BTraceProbe instance * @param simpleHandlerName the unqualified handler method name (e.g. "onMethod") * @param handlerType the MethodType of the handler * @return the handler, possibly wrapped with a level guard */ private static MethodHandle applyLevelGuard( MethodHandle handler, BTraceProbe probe, String simpleHandlerName, MethodType handlerType) { // Find the OnMethod metadata for this handler Level level = null; for (OnMethod om : probe.onmethods()) { if (om.getMethod().equals(simpleHandlerName)) { level = om.getLevel(); if (level != null) { break; } } } if (level == null) { return handler; // No level guard needed } // Compose the handler with a level guard. The guard will: // 1. Check if current instrumentation level satisfies the requirement // 2. If yes, invoke the real handler // 3. If no, invoke a noop that returns the default value for the type log.debug("Handler {} requires level check: {}", simpleHandlerName, level.getValue()); try { // Create a test MethodHandle that checks the level requirement. // It takes the same arguments as the handler but returns boolean. MethodType testType = handlerType.changeReturnType(boolean.class); MethodHandle levelTest = createLevelCheckMH(level, testType); // Create a noop handler that returns default value for the return type MethodHandle noop = createNoopMH(handlerType); // Compose: if level check passes, invoke handler; otherwise noop return MethodHandles.guardWithTest(levelTest, handler, noop); } catch (Throwable e) { log.warn("Failed to create level guard for handler {}", simpleHandlerName, e); return handler; // Fall back to unguarded handler on error } } /** * Create a MethodHandle that tests if the current instrumentation level satisfies * the given level requirement. Returns true if level check passes, false otherwise. */ private static MethodHandle createLevelCheckMH(Level level, MethodType testType) throws Throwable { // Get the Interval requirement (e.g., ">=100" → [100, MAX_VALUE]) Interval interval = level.getValue(); // Create a static helper method that checks: currentLevel >= a && currentLevel <= b // We need to invoke this helper with the level bounds and call the level getter // The tricky part: we need access to BTraceRuntime to query the current level, // but we don't have it as a parameter. We must create a method that can access it. // Use a custom MethodHandle that invokes our level-checking logic MethodHandle levelChecker = createLevelCheckerMH(interval); // Drop arguments to match testType (accept same params as handler, return boolean) return MethodHandles.dropArguments(levelChecker, 0, testType.parameterArray()); } /** * Helper: create a MethodHandle that queries BTraceRuntime.getInstrumentationLevel() * and checks if it falls within the given interval. */ private static MethodHandle createLevelCheckerMH(Interval interval) throws Throwable { int minLevel = interval.getA(); int maxLevel = interval.getB(); // Create a MethodHandle that returns true if currentLevel is in [minLevel, maxLevel] MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle checkMH = lookup.findStatic( HandlerRepositoryImpl.class, "checkLevelInRange", MethodType.methodType(boolean.class, int.class, int.class, int.class)); // Bind the interval bounds as arguments at positions 0 and 1 // After binding: (minLevel=bound, maxLevel=bound, currentLevel=?) -> boolean MethodHandle bound = MethodHandles.insertArguments(checkMH, 0, minLevel, maxLevel); // After binding: (currentLevel) -> boolean // Compose with a getter that queries the current level MethodHandle getCurrentLevelMH = lookup.findStatic( BTraceRuntimes.class, "getCurrentLevel", MethodType.methodType(int.class)); // getCurrentLevel: () -> int // Fold: getCurrentLevel() returns int, pass result as first arg to bound // Result: () -> boolean return MethodHandles.foldArguments(bound, getCurrentLevelMH); } /** * Static helper invoked via MethodHandle: check if level is in [min, max] range. */ @SuppressWarnings("unused") // invoked via MethodHandle private static boolean checkLevelInRange(int minLevel, int maxLevel, int currentLevel) { return currentLevel >= minLevel && currentLevel <= maxLevel; } /** * Create a noop MethodHandle that returns the default value for the given type. */ private static MethodHandle createNoopMH(MethodType handlerType) throws Throwable { Class returnType = handlerType.returnType(); // Reuse shared default value logic from IndyDispatcher Object defaultValue = IndyDispatcher.defaultValueFor(returnType); MethodHandle noop = MethodHandles.constant(returnType, defaultValue); // Drop arguments so the noop accepts the handler's parameters but ignores them return MethodHandles.dropArguments(noop, 0, handlerType.parameterArray()); } /** * Resolve a probe handler MethodHandle. Called from IndyDispatcher.bootstrap() on first * execution of each instrumented call site, and subsequently from the trampoline on * every retry until resolution succeeds. * * @param probeName internal class name of the probe (e.g. {@code "com/example/MyTrace"}) * @param handlerName handler method name (probe-prefixed, e.g. {@code "MyTrace$onMethod"}) * @param handlerType the MethodType of the call site * @return the resolved MethodHandle, or {@code null} if resolution fails (caller must * treat null as transient and retry) */ public static MethodHandle resolveHandler( String probeName, String handlerName, MethodType handlerType) { BTraceProbe probe = probeMap.get(probeName); if (probe == null) { // Probe not registered yet. Do not cache — IndyDispatcher's trampoline will retry. log.debug("[INDY] probe {} not registered, probeMap.size={}", probeName, probeMap.size()); return null; } MethodHandle cached = probe.getCachedHandler(handlerName, handlerType); if (cached != null) { log.debug("[INDY] cached handler {} for {}", cached, handlerName); return cached; } Class probeClass = probe.getProbeClass(); if (probeClass == null) { // defineClass has not populated probeClass yet (race with register()). // Do not cache — IndyDispatcher's trampoline will retry. log.debug("[INDY] probeClass null for {}", probeName); return null; } log.debug("[INDY] resolving {}.{} in {}", probeName, handlerName, probeClass.getClassLoader()); try { // Strip probe-name prefix from handler name (e.g. "MyTrace$onMethod" → "onMethod") int dollarIdx = handlerName.lastIndexOf('$'); String simpleHandlerName = dollarIdx >= 0 ? handlerName.substring(dollarIdx + 1) : handlerName; MethodHandle mh; try { // Try public lookup first (works for normal, accessible classes). mh = MethodHandles.publicLookup().findStatic(probeClass, simpleHandlerName, handlerType); } catch (IllegalAccessException publicLookupFailed) { // publicLookup has limited access across classloader boundaries. For probes defined // in isolated/unnamed ClassLoaders (per-probe isolation on JDK 8/9-14, or hidden // classes on JDK 15+), publicLookup cannot see the handler methods. Fall back to // a reflection-based approach: get the method via getDeclaredMethod, then convert // it to a MethodHandle using unreflect. This works on all JDK versions. log.debug( "publicLookup denied access to {}.{}, falling back to reflection-based resolution", probeName, simpleHandlerName); try { // Extract parameter types from the handler MethodType. Class[] paramTypes = new Class[handlerType.parameterCount()]; for (int i = 0; i < paramTypes.length; i++) { paramTypes[i] = handlerType.parameterType(i); } java.lang.reflect.Method method = probeClass.getDeclaredMethod(simpleHandlerName, paramTypes); method.setAccessible(true); mh = MethodHandles.lookup().unreflect(method); } catch (Throwable fallbackEx) { // Reflection-based approach also failed. Re-throw the original exception from // publicLookup so it gets logged with proper context in the outer catch. throw publicLookupFailed; } } // Apply level guard if this handler has a level check mh = applyLevelGuard(mh, probe, simpleHandlerName, handlerType); probe.cacheHandler(handlerName, handlerType, mh); if (SharedSettings.GLOBAL.isDumpClasses()) { log.debug("BTrace INDY handler resolved: {}.{}", probeName, simpleHandlerName); } return mh; } catch (Throwable e) { // Log loudly: unlike transient null-repository or unregistered-probe failures, // findStatic exceptions usually mean a real problem (signature mismatch, module // access). Don't cache — the trampoline will retry, but the same failure is likely // to recur until the probe class/bytecode is fixed. log.warn("Failed to resolve handler '{}' in probe '{}'", handlerName, probeName, e); return null; } } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrPackGenerator.java ================================================ package org.openjdk.btrace.instr; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import org.openjdk.btrace.compiler.PackGenerator; import org.openjdk.btrace.core.SharedSettings; public final class InstrPackGenerator implements PackGenerator { @Override public byte[] generateProbePack(byte[] classData) throws IOException { BTraceProbeNode bpn = (BTraceProbeNode) new BTraceProbeFactory(SharedSettings.GLOBAL).createProbe(classData); // force bytecode verification before creating the persisted representation bpn.checkVerified(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (DataOutputStream dos = new DataOutputStream(bos)) { BTraceProbePersisted bpp = BTraceProbePersisted.from(bpn); if (!bpp.isVerified()) { throw new Error(); } bpp.write(dos); } return bos.toByteArray(); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentUtils.java ================================================ /* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; import static org.objectweb.asm.Opcodes.*; import static org.openjdk.btrace.instr.TypeUtils.isAnyType; import static org.openjdk.btrace.instr.TypeUtils.isPrimitive; import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Type; /** * @author A. Sundararajan * @author J. Bachorik */ public final class InstrumentUtils { private static final int CW_FLAGS = 0; // ClassWriter.COMPUTE_MAXS; /** * Collects the type hierarchy into the provided list, sorted from the actual type to root. Common * superclasses may be present multiple times (eg. {@code java.lang.Object}) It will use the * associated classloader to locate the class file resources. * * @param cl the associated classloader * @param type the type to compute the hierarchy closure for (either Java or internal name format) * @param closure the ordered set to store the closure in * @param useInternal should internal types names be used in the closure */ public static void collectHierarchyClosure( ClassLoader cl, String type, Set closure, boolean useInternal) { collectHierarchyClosure(cl, type, closure, useInternal, false); } /** * Collects the type hierarchy into the provided list, sorted from the actual type to root. Common * superclasses may be present multiple times (eg. {@code java.lang.Object}) It will use the * associated classloader to locate the class file resources. * * @param cl the associated classloader * @param type the type to compute the hierarchy closure for (either Java or internal name format) * @param closure the ordered set to store the closure in * @param useInternal should internal types names be used in the closure */ public static void collectHierarchyClosure( ClassLoader cl, String type, Set closure, boolean useInternal, boolean ifcs) { if (type == null || type.equals(Constants.OBJECT_INTERNAL)) { return; } ClassInfo ci = ClassCache.getInstance().get(cl, type); Set ciSet = new LinkedHashSet<>(); // add self ciSet.add(ci); for (ClassInfo sci : ci.getSupertypes(false)) { if ((ifcs || !sci.isInterface()) && !sci.getClassName().equals(Constants.OBJECT_INTERNAL)) { ciSet.add(sci); } } for (ClassInfo sci : ciSet) { closure.add(useInternal ? sci.getClassName() : sci.getJavaClassName()); } } public static boolean isAssignable( Type left, Type right, ClassLoader cl, boolean exactTypeCheck) { boolean isSame = left.equals(right); if (isSame) { return true; } if (TypeUtils.isVoid(left)) { return TypeUtils.isVoid(right); } if (TypeUtils.isAnyType(left)) { return true; } if (exactTypeCheck) { return false; } if (TypeUtils.isObject(left)) { return true; } Set closure = new HashSet<>(); collectHierarchyClosure(cl, right.getInternalName(), closure, true, true); return closure.contains(left.getInternalName()); } public static boolean isAssignable( Type[] args1, Type[] args2, ClassLoader cl, boolean exactTypeCheck) { if (args1.length != args2.length) { return false; } for (int i = 0; i < args1.length; i++) { if (!args1[i].equals(args2[i])) { int sort2 = args2[i].getSort(); /* * if destination is AnyType and right side is * Object or Array (i.e., any reference type) * then we allow it - because AnyType is mapped to * java.lang.Object. */ if (!(isAnyType(args1[i]) && (sort2 == Type.OBJECT || sort2 == Type.ARRAY || isPrimitive(args2[i])))) { if (!isAssignable(args1[i], args2[i], cl, exactTypeCheck)) { return false; } } } } return true; } public static String arrayDescriptorFor(int typeCode) { switch (typeCode) { case T_BOOLEAN: return "[Z"; case T_CHAR: return "[C"; case T_FLOAT: return "[F"; case T_DOUBLE: return "[D"; case T_BYTE: return "[B"; case T_SHORT: return "[S"; case T_INT: return "[I"; case T_LONG: return "[J"; default: throw new IllegalArgumentException(); } } public static void accept(BTraceClassReader reader, ClassVisitor visitor) { accept(reader, visitor, 0); } public static void accept(BTraceClassReader reader, ClassVisitor visitor, int flags) { if (reader == null || visitor == null) return; reader.accept(visitor, flags); } private static boolean isJDK16OrAbove(byte[] code) { return isJDK16OrAbove(getMajor(code)); } private static boolean isJDK16OrAbove(BTraceClassReader cr) { return isJDK16OrAbove(getMajor(cr)); } private static boolean isJDK16OrAbove(int major) { return major >= 50; } private static int getMajor(BTraceClassReader cr) { return cr.getClassVersion(); } private static int getMajor(byte[] code) { // skip 0xCAFEBABE magic and minor version int majorOffset = 4 + 2; return (((code[majorOffset] << 8) & 0xFF00) | ((code[majorOffset + 1]) & 0xFF)); } public static ClassWriter newClassWriter() { return newClassWriter(false); } public static ClassWriter newClassWriter(boolean computeFrames) { return newClassWriter(computeFrames, false); } public static ClassWriter newClassWriter(boolean computeFrames, boolean computeMaxs) { int flags = CW_FLAGS; if (computeFrames) { flags |= ClassWriter.COMPUTE_FRAMES; } if (computeMaxs) { flags |= ClassWriter.COMPUTE_MAXS; } return newClassWriter(null, flags); } static BTraceClassWriter newClassWriter(BTraceClassReader cr) { return newClassWriter(cr, CW_FLAGS); } static BTraceClassWriter newClassWriter(BTraceClassReader reader, int flags) { BTraceClassWriter cw; cw = reader != null ? new BTraceClassWriter(reader.getClassLoader(), reader, flags) : new BTraceClassWriter(null, flags); return cw; } static BTraceClassReader newClassReader(byte[] code) { return new BTraceClassReader(ClassLoader.getSystemClassLoader(), code); } static BTraceClassReader newClassReader(ClassLoader cl, byte[] code) { return new BTraceClassReader(cl, code); } static BTraceClassReader newClassReader(InputStream is) throws IOException { return new BTraceClassReader(ClassLoader.getSystemClassLoader(), is); } static BTraceClassReader newClassReader(ClassLoader cl, InputStream is) throws IOException { return new BTraceClassReader(cl, is); } static String getActionPrefix(String className) { return Constants.BTRACE_METHOD_PREFIX + className.replace('/', '$') + "$"; } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentationException.java ================================================ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.btrace.instr; /** * Thrown when bytecode instrumentation fails due to invalid variable mapping, corrupted state, or * other instrumentation logic errors. * *

This exception indicates a bug in the instrumentation logic and provides detailed debug * information to help diagnose the issue. When thrown, instrumentation will fail gracefully, * returning the original unmodified class to prevent crashes in the instrumented application. * * @author BTrace Team * @since 2.3 */ public class InstrumentationException extends RuntimeException { public InstrumentationException(String message) { super(message); } public InstrumentationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentingMethodVisitor.java ================================================ /* * Copyright (c) 2017, Jaroslav Bachorik . * All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Copyright owner designates * this particular file as subject to the "Classpath" exception as provided * by the owner in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openjdk.btrace.instr; import static org.objectweb.asm.Opcodes.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A method visitor providing support for introducing new local variables in bytecode recomputing * stackmap frames as necessary. It also provides an API for downstream visitors to hint insertion * of stackmap frames at required locations. */ public final class InstrumentingMethodVisitor extends MethodVisitor implements MethodInstrumentorHelper { private static final Logger log = LoggerFactory.getLogger(InstrumentingMethodVisitor.class); interface FrameDiagnosticListener { void onFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack); } static final Object TOP_EXT = -2; private final VariableMapper variableMapper; private final SimulatedStack stack = new SimulatedStack(); private final List locals = new ArrayList<>(); private final Set newLocals = new HashSet<>(3); private final LocalVarTypes localTypes = new LocalVarTypes(); private final Set frameOffsets = new HashSet<>(); private final Map jumpTargetStates = new HashMap<>(); private final Map> tryCatchHandlerMap = new HashMap<>(); private final Map trackingContexts = new HashMap<>(); private final String owner; private final String desc; private int argsSize = 0; private boolean shouldCacheLevelVar = false; private int localsTailPtr = 0; private int pc = 0, lastFramePc = Integer.MIN_VALUE; private final FrameDiagnosticListener frameDiagnosticListener; public InstrumentingMethodVisitor(int access, String owner, String name, String desc, MethodVisitor mv) { this(access, owner, name, desc, mv, (type, nLocal, local, nStack, stack) -> {}); } InstrumentingMethodVisitor( int access, String owner, String name, String desc, MethodVisitor mv, FrameDiagnosticListener frameDiagnosticListener) { super(ASM9, mv); this.owner = owner; this.desc = desc; initLocals((access & ACC_STATIC) == 0, "".equals(name)); variableMapper = new VariableMapper(argsSize); this.frameDiagnosticListener = frameDiagnosticListener; } private static Object toSlotType(Type t) { if (t == null) { return null; } switch (t.getSort()) { case Type.BOOLEAN: case Type.CHAR: case Type.BYTE: case Type.SHORT: case Type.INT: { return INTEGER; } case Type.FLOAT: { return FLOAT; } case Type.LONG: { return LONG; } case Type.DOUBLE: { return DOUBLE; } default: { return t == Constants.NULL_TYPE ? NULL : (t == Constants.TOP_TYPE ? TOP : t.getInternalName()); } } } /** * Normalizes a frame array by replacing BTrace-specific TOP_EXT markers with ASM's TOP constant. * TOP_EXT is used internally to mark the second slot of long/double values, but ASM expects TOP * instead. */ private static Object[] normalizeFrameArray(Object[] arr) { if (arr == null || arr.length == 0) { return arr; } Object[] result = new Object[arr.length]; for (int i = 0; i < arr.length; i++) { result[i] = arr[i] == TOP_EXT ? TOP : arr[i]; } return result; } @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { if (lastFramePc == pc) { return; } lastFramePc = pc; switch (type) { case F_NEW: // fallthrough case F_FULL: { locals.clear(); this.stack.reset(); if (nLocal > 0 && locals != null) { locals.addAll(Arrays.asList(local).subList(0, nLocal)); localsTailPtr = nLocal; } else { localsTailPtr = 0; } if (nStack > 0 && stack != null) { for (int i = 0; i < nStack; i++) { Object e = stack[i]; this.stack.push(e); } } break; } case F_SAME: { this.stack.reset(); break; } case F_SAME1: { this.stack.reset(); Object e = stack[0]; this.stack.push(e); break; } case F_APPEND: { this.stack.reset(); int top = locals.size(); for (int i = 0; i < nLocal; i++) { Object e = local[i]; if (localsTailPtr < top) { locals.set(localsTailPtr, e); } else { locals.add(e); } localsTailPtr++; } break; } case F_CHOP: { this.stack.reset(); for (int i = 0; i < nLocal; i++) { locals.remove(--localsTailPtr); } break; } } Object[] localsArr = computeFrameLocals(); localTypes.replaceWith(localsArr); int off = 0; for (int i = 0; i < localsArr.length; i++) { Object val = localsArr[i]; if (val == TOP_EXT) { off++; continue; } if (off > 0) { localsArr[i - off] = localsArr[i]; } } localsArr = Arrays.copyOf(localsArr, localsArr.length - off); Object[] tmpStack = this.stack.toArray(true); superVisitFrame(F_NEW, localsArr.length, localsArr, tmpStack.length, tmpStack); } void superVisitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { frameDiagnosticListener.onFrame(type, nLocal, local, nStack, stack); super.visitFrame(type, nLocal, local, nStack, stack); } @Override public void visitMultiANewArrayInsn(String arrayTypeName, int dims) { Type arrayType = Type.getObjectType(arrayTypeName); for (int i = 0; i < dims; i++) { stack.pop(); } stack.push(arrayType.getDescriptor()); super.visitMultiANewArrayInsn(arrayTypeName, dims); pc++; } @Override public void visitLookupSwitchInsn(Label label, int[] ints, Label[] labels) { stack.pop(); SavedState state = new SavedState(localTypes, stack, newLocals, SavedState.CONDITIONAL); jumpTargetStates.put(label, state); for (Label l : labels) { jumpTargetStates.put(l, state); } super.visitLookupSwitchInsn(label, ints, labels); pc++; } @Override public void visitTableSwitchInsn(int i, int i1, Label label, Label... labels) { stack.pop(); SavedState state = new SavedState(localTypes, stack, newLocals, SavedState.CONDITIONAL); jumpTargetStates.put(label, state); for (Label l : labels) { jumpTargetStates.put(l, state); } super.visitTableSwitchInsn(i, i1, label, labels); pc++; } @Override public void visitLdcInsn(Object o) { Type t = Type.getType(o.getClass()); switch (t.getInternalName()) { case "java/lang/Integer": { pushToStack(Type.INT_TYPE); break; } case "java/lang/Long": { pushToStack(Type.LONG_TYPE); break; } case "java/lang/Byte": { pushToStack(Type.BYTE_TYPE); break; } case "java/lang/Short": { pushToStack(Type.SHORT_TYPE); break; } case "java/lang/Character": { pushToStack(Type.CHAR_TYPE); break; } case "java/lang/Boolean": { pushToStack(Type.BOOLEAN_TYPE); break; } case "java/lang/Float": { pushToStack(Type.FLOAT_TYPE); break; } case "java/lang/Double": { pushToStack(Type.DOUBLE_TYPE); break; } default: { pushToStack(t); } } super.visitLdcInsn(o); pc++; } @Override public void visitJumpInsn(int opcode, Label label) { super.visitJumpInsn(opcode, label); pc++; switch (opcode) { case IFEQ: case IFGE: case IFGT: case IFLE: case IFLT: case IFNE: case IFNONNULL: case IFNULL: { stack.pop(); break; } case IF_ACMPEQ: case IF_ACMPNE: case IF_ICMPEQ: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPNE: { stack.pop(); stack.pop(); break; } } jumpTargetStates.put( label, new SavedState( localTypes, stack, newLocals, opcode == GOTO || opcode == JSR ? SavedState.UNCONDITIONAL : SavedState.CONDITIONAL)); } @Override public void visitInvokeDynamicInsn(String name, String desc, Handle handle, Object... bsmArgs) { Type[] args = Type.getArgumentTypes(desc); Type ret = Type.getReturnType(desc); for (int i = args.length - 1; i >= 0; i--) { if (!args[i].equals(Type.VOID_TYPE)) { popFromStack(args[i]); } } super.visitInvokeDynamicInsn(name, desc, handle, bsmArgs); pc++; if (!ret.equals(Type.VOID_TYPE)) { pushToStack(ret); } } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itfc) { Type[] args = Type.getArgumentTypes(desc); Type ret = Type.getReturnType(desc); for (int i = args.length - 1; i >= 0; i--) { if (!args[i].equals(Type.VOID_TYPE)) { popFromStack(args[i]); } } if (opcode != INVOKESTATIC) { stack.pop(); } super.visitMethodInsn(opcode, owner, name, desc, itfc); pc++; if (!ret.equals(Type.VOID_TYPE)) { pushToStack(ret); } if (opcode == INVOKESPECIAL && name.equals("")) { if (stack.peek() instanceof Label) { stack.pop(); pushToStack(Type.getObjectType(owner)); } else { Object obj = stack.peek(); // uninitialized this becomes initialized after call to super if (!(obj instanceof String)) { locals.replaceAll(o -> o == UNINITIALIZED_THIS ? this.owner : o); localTypes.resolveUnitializedThis(this.owner); } } } } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { Type t = Type.getType(desc); super.visitFieldInsn(opcode, owner, name, desc); pc++; if (opcode == PUTFIELD || opcode == PUTSTATIC) { popFromStack(t); } if (opcode == GETFIELD || opcode == PUTFIELD) { stack.pop(); // pop 'this' } if (opcode == GETFIELD || opcode == GETSTATIC) { pushToStack(t); } } @Override public void visitTypeInsn(int opcode, String type) { super.visitTypeInsn(opcode, type); pc++; switch (opcode) { case NEW: { pushToStack(Type.getObjectType(type)); break; } case ANEWARRAY: { stack.pop(); Type elementType = type.endsWith(";") ? Type.getType(type) : Type.getObjectType(type); Type arrayType = Type.getType("[" + elementType); pushToStack(arrayType); break; } case INSTANCEOF: { stack.pop(); pushToStack(Type.BOOLEAN_TYPE); break; } case CHECKCAST: { stack.pop(); pushToStack(Type.getObjectType(type)); break; } } } @Override public void visitVarInsn(int opcode, int var) { int size = 1; switch (opcode) { case DLOAD: case LLOAD: case DSTORE: case LSTORE: { size++; break; } } var = variableMapper.remap(var, size); boolean isPush = false; Type opType = null; switch (opcode) { case ILOAD: { opType = Type.INT_TYPE; isPush = true; break; } case LLOAD: { opType = Type.LONG_TYPE; isPush = true; break; } case FLOAD: { opType = Type.FLOAT_TYPE; isPush = true; break; } case DLOAD: { opType = Type.DOUBLE_TYPE; isPush = true; break; } case ALOAD: { Object o = localTypes.getType(var); opType = fromSlotType(o); isPush = true; break; } case ISTORE: { opType = Type.INT_TYPE; break; } case LSTORE: { opType = Type.LONG_TYPE; break; } case FSTORE: { opType = Type.FLOAT_TYPE; break; } case DSTORE: { opType = Type.DOUBLE_TYPE; break; } case ASTORE: { opType = fromSlotType(stack.peek()); break; } } assert opType != null; if (isPush) { pushToStack(opType); } else { popFromStack(opType); localTypes.setType(var, opType); } super.visitVarInsn(opcode, var); pc++; } @Override public void visitIntInsn(int opcode, int operand) { super.visitIntInsn(opcode, operand); pc++; switch (opcode) { case BIPUSH: case SIPUSH: { stack.push(INTEGER); break; } case NEWARRAY: { popFromStack(Type.INT_TYPE); // size switch (operand) { case T_BOOLEAN: { pushToStack(Type.getObjectType("[Z")); break; } case T_CHAR: { pushToStack(Type.getObjectType("[C")); break; } case T_FLOAT: { pushToStack(Type.getObjectType("[F")); break; } case T_DOUBLE: { pushToStack(Type.getObjectType("[D")); break; } case T_BYTE: { pushToStack(Type.getObjectType("[B")); break; } case T_SHORT: { pushToStack(Type.getObjectType("[S")); break; } case T_INT: { pushToStack(Type.getObjectType("[I")); break; } case T_LONG: { pushToStack(Type.getObjectType("[J")); break; } } break; } } } @Override public void visitInsn(int opcode) { super.visitInsn(opcode); pc++; switch (opcode) { case ACONST_NULL: { stack.push(NULL); break; } case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: case ICONST_M1: { pushToStack(Type.INT_TYPE); break; } case FCONST_0: case FCONST_1: case FCONST_2: { pushToStack(Type.FLOAT_TYPE); break; } case LCONST_0: case LCONST_1: { pushToStack(Type.LONG_TYPE); break; } case DCONST_0: case DCONST_1: { pushToStack(Type.DOUBLE_TYPE); break; } case AALOAD: { stack.pop(); // index Object target = stack.pop(); if (target instanceof String) { Type t; String typeStr = (String) target; if (typeStr.startsWith("[")) { if (typeStr.contains("/") && !typeStr.endsWith(";")) { typeStr += ";"; } // Type.getElementType() will give the non-array type which we don't want here // For a multi-dimensional array the element type is the lower dimension array typeStr = typeStr.substring(1); t = Type.getType(typeStr); } else { t = Type.getObjectType(typeStr); } pushToStack(t); } else if (target == NULL) { pushToStack(Constants.NULL_TYPE); } else { pushToStack(Constants.OBJECT_TYPE); } break; } case IALOAD: { stack.pop(); stack.pop(); pushToStack(Type.INT_TYPE); break; } case FALOAD: { stack.pop(); stack.pop(); pushToStack(Type.FLOAT_TYPE); break; } case BALOAD: { stack.pop(); stack.pop(); pushToStack(Type.BYTE_TYPE); break; } case CALOAD: { stack.pop(); stack.pop(); pushToStack(Type.CHAR_TYPE); break; } case SALOAD: { stack.pop(); stack.pop(); pushToStack(Type.SHORT_TYPE); break; } case LALOAD: { stack.pop(); stack.pop(); pushToStack(Type.LONG_TYPE); break; } case DALOAD: { stack.pop(); stack.pop(); pushToStack(Type.DOUBLE_TYPE); break; } case AASTORE: case IASTORE: case FASTORE: case BASTORE: case CASTORE: case SASTORE: case LASTORE: case DASTORE: { stack.pop(); // val stack.pop(); // index stack.pop(); // arrayref break; } case POP: { stack.pop1(); break; } case POP2: { stack.pop1(); stack.pop1(); break; } case DUP: { stack.push1(stack.peek()); break; } case DUP_X1: { Object x = stack.pop1(); Object y = stack.pop1(); stack.push1(x); stack.push1(y); stack.push1(x); break; } case DUP_X2: { Object x = stack.pop1(); Object y = stack.pop1(); Object z = stack.pop1(); stack.push1(x); stack.push1(z); stack.push1(y); stack.push1(x); break; } case DUP2: { Object x = stack.pop1(); Object y = stack.peek(); stack.push1(x); stack.push1(y); stack.push1(x); break; } case DUP2_X1: { Object x2 = stack.pop1(); Object x1 = stack.pop1(); Object y = stack.pop1(); stack.push1(x1); stack.push1(x2); stack.push1(y); stack.push1(x1); stack.push1(x2); break; } case DUP2_X2: { Object x2 = stack.pop1(); Object x1 = stack.pop1(); Object y2 = stack.pop1(); Object y1 = stack.pop1(); stack.push1(x1); stack.push1(x2); stack.push1(y1); stack.push1(y2); stack.push1(x1); stack.push1(x2); break; } case SWAP: { Object x = stack.pop1(); Object y = stack.pop1(); stack.push1(x); stack.push1(y); break; } case IADD: case ISUB: case IMUL: case IDIV: case IREM: case IAND: case IOR: case IXOR: case ISHR: case ISHL: case IUSHR: { popFromStack(Type.INT_TYPE); popFromStack(Type.INT_TYPE); pushToStack(Type.INT_TYPE); break; } case FADD: case FSUB: case FMUL: case FDIV: case FREM: { popFromStack(Type.FLOAT_TYPE); popFromStack(Type.FLOAT_TYPE); pushToStack(Type.FLOAT_TYPE); break; } case LADD: case LSUB: case LMUL: case LDIV: case LREM: case LAND: case LOR: case LXOR: { popFromStack(Type.LONG_TYPE); popFromStack(Type.LONG_TYPE); pushToStack(Type.LONG_TYPE); break; } case LSHR: case LSHL: case LUSHR: { popFromStack(Type.INT_TYPE); popFromStack(Type.LONG_TYPE); pushToStack(Type.LONG_TYPE); break; } case DADD: case DSUB: case DMUL: case DDIV: case DREM: { popFromStack(Type.DOUBLE_TYPE); popFromStack(Type.DOUBLE_TYPE); pushToStack(Type.DOUBLE_TYPE); break; } case I2L: { popFromStack(Type.INT_TYPE); pushToStack(Type.LONG_TYPE); break; } case I2F: { popFromStack(Type.INT_TYPE); pushToStack(Type.FLOAT_TYPE); break; } case I2B: { popFromStack(Type.INT_TYPE); pushToStack(Type.BYTE_TYPE); break; } case I2C: { popFromStack(Type.INT_TYPE); pushToStack(Type.CHAR_TYPE); break; } case I2S: { popFromStack(Type.INT_TYPE); pushToStack(Type.SHORT_TYPE); break; } case I2D: { popFromStack(Type.INT_TYPE); pushToStack(Type.DOUBLE_TYPE); break; } case L2I: { popFromStack(Type.LONG_TYPE); pushToStack(Type.INT_TYPE); break; } case L2F: { popFromStack(Type.LONG_TYPE); pushToStack(Type.FLOAT_TYPE); break; } case L2D: { popFromStack(Type.LONG_TYPE); pushToStack(Type.DOUBLE_TYPE); break; } case F2I: { popFromStack(Type.FLOAT_TYPE); pushToStack(Type.INT_TYPE); break; } case F2L: { popFromStack(Type.FLOAT_TYPE); pushToStack(Type.LONG_TYPE); break; } case F2D: { popFromStack(Type.FLOAT_TYPE); pushToStack(Type.DOUBLE_TYPE); break; } case D2I: { popFromStack(Type.DOUBLE_TYPE); pushToStack(Type.INT_TYPE); break; } case D2F: { popFromStack(Type.DOUBLE_TYPE); pushToStack(Type.FLOAT_TYPE); break; } case D2L: { popFromStack(Type.DOUBLE_TYPE); pushToStack(Type.LONG_TYPE); break; } case LCMP: { popFromStack(Type.LONG_TYPE); popFromStack(Type.LONG_TYPE); pushToStack(Type.INT_TYPE); break; } case FCMPL: case FCMPG: { popFromStack(Type.FLOAT_TYPE); popFromStack(Type.FLOAT_TYPE); pushToStack(Type.INT_TYPE); break; } case DCMPL: case DCMPG: { popFromStack(Type.DOUBLE_TYPE); popFromStack(Type.DOUBLE_TYPE); pushToStack(Type.INT_TYPE); break; } case IRETURN: { popFromStack(Type.INT_TYPE); break; } case LRETURN: { popFromStack(Type.LONG_TYPE); break; } case FRETURN: { popFromStack(Type.FLOAT_TYPE); break; } case DRETURN: { popFromStack(Type.DOUBLE_TYPE); break; } case ARETURN: { popFromStack(Type.getReturnType(desc)); break; } case ATHROW: { popFromStack(Constants.THROWABLE_TYPE); break; } case ARRAYLENGTH: { stack.pop(); pushToStack(Type.INT_TYPE); break; } case MONITORENTER: case MONITOREXIT: { stack.pop(); break; } } } @Override public void visitIincInsn(int var, int increment) { super.visitIincInsn(variableMapper.remap(var, 1), increment); pc++; } @Override public void visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index) { try { int newIndex = variableMapper.map(index, start); super.visitLocalVariable( name, desc, signature, start, end, newIndex == Integer.MIN_VALUE ? 0 : newIndex); } catch (InstrumentationException e) { throw new InstrumentationException( String.format( "Failed to map local variable '%s' (type=%s, index=%d, scope=%s): %s", name, desc, index, start, e.getMessage()), e); } } @Override public AnnotationVisitor visitLocalVariableAnnotation( int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { try { int[] newIndex = new int[index.length]; for (int i = 0; i < index.length; i++) { newIndex[i] = variableMapper.map(index[i]); } return super.visitLocalVariableAnnotation( typeRef, typePath, start, end, newIndex, desc, visible); } catch (InstrumentationException e) { throw new InstrumentationException( String.format( "Failed to map local variable annotation (type=%s, indices=%s): %s", desc, Arrays.toString(index), e.getMessage()), e); } } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String exception) { addTryCatchHandler(start, handler); super.visitTryCatchBlock(start, end, handler, exception); } @Override public void visitLabel(Label label) { variableMapper.noteLabel(label); SavedState ss = jumpTargetStates.get(label); if (ss != null) { if (ss.kind != SavedState.CONDITIONAL) { reset(); } localTypes.mergeWith(ss.lvTypes.toArray()); stack.replaceWith(ss.sStack.toArray()); if (ss.kind == SavedState.EXCEPTION) { stack.push(toSlotType(Constants.THROWABLE_TYPE)); } for (LocalVarSlot lvs : newLocals) { if (!ss.newLocals.contains(lvs)) { lvs.expire(); } } newLocals.clear(); newLocals.addAll(ss.newLocals); } Set