Quellverzeichnis CycleCollectedJSRuntime.cpp
Sprache: C
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// We're dividing JS objects into 3 categories: // // 1. "real" roots, held by the JS engine itself or rooted through the root // and lock JS APIs. Roots from this category are considered black in the // cycle collector, any cycle they participate in is uncollectable. // // 2. certain roots held by C++ objects that are guaranteed to be alive. // Roots from this category are considered black in the cycle collector, // and any cycle they participate in is uncollectable. These roots are // traced from TraceNativeBlackRoots. // // 3. all other roots held by C++ objects that participate in cycle collection, // held by us (see TraceNativeGrayRoots). Roots from this category are // considered grey in the cycle collector; whether or not they are collected // depends on the objects that hold them. // // Note that if a root is in multiple categories the fact that it is in // category 1 or 2 that takes precedence, so it will be considered black. // // During garbage collection we switch to an additional mark color (gray) when // tracing inside TraceNativeGrayRoots. This allows us to walk those roots later // on and add all objects reachable only from them to the cycle collector. // // Phases: // // 1. marking of the roots in category 1 by having the JS GC do its marking // 2. marking of the roots in category 2 by having the JS GC call us back // (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots // 3. marking of the roots in category 3 by // TraceNativeGrayRootsInCollectingZones using an additional color (gray). // 4. end of GC, GC can sweep its heap // // At some later point, when the cycle collector runs: // // 5. walk gray objects and add them to the cycle collector, cycle collect // // JS objects that are part of cycles the cycle collector breaks will be // collected by the next JS GC. // // If WantAllTraces() is false the cycle collector will not traverse roots // from category 1 or any JS objects held by them. Any JS objects they hold // will already be marked by the JS GC and will thus be colored black // themselves. Any C++ objects they hold will have a missing (untraversed) // edge from the JS object to the C++ object and so it will be marked black // too. This decreases the number of objects that the cycle collector has to // deal with. // To improve debugging, if WantAllTraces() is true all JS objects are // traversed.
#ifdef NIGHTLY_BUILD // For performance reasons, we make the JS Dev Error Interceptor a Nightly-only // feature. # define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1 #endif// NIGHTLY_BUILD
void NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey,
JS::GCCellPtr aValue) { // If nothing that could be held alive by this entry is marked gray, return. if ((!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
MOZ_LIKELY(!mCb.WantAllTraces())) { if (!aValue || !JS::GCThingIsMarkedGrayInCC(aValue) ||
aValue.is<JSString>()) { return;
}
}
// The cycle collector can only properly reason about weak maps if it can // reason about the liveness of their keys, which in turn requires that // the key can be represented in the cycle collector graph. All existing // uses of weak maps use either objects or scripts as keys, which are okay.
MOZ_ASSERT(JS::IsCCTraceKind(aKey.kind()));
// As an emergency fallback for non-debug builds, if the key is not // representable in the cycle collector graph, we treat it as marked. This // can cause leaks, but is preferable to ignoring the binding, which could // cause the cycle collector to free live objects. if (!JS::IsCCTraceKind(aKey.kind())) {
aKey = nullptr;
}
if (!aValue.is<JSString>()) {
JS::TraceChildren(&mChildTracer, aValue);
}
// The delegate could hold alive the key, so report something to the CC // if we haven't already. if (!mChildTracer.mTracedAny && aKey && JS::GCThingIsMarkedGrayInCC(aKey) &&
kdelegate) {
mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr);
}
}
}
// Report whether the key or value of a weak mapping entry are gray but need to // be marked black. staticvoid ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey,
JS::GCCellPtr aValue, bool* aKeyShouldBeBlack, bool* aValueShouldBeBlack) {
*aKeyShouldBeBlack = false;
*aValueShouldBeBlack = false;
// If nothing that could be held alive by this entry is marked gray, return. bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGrayInCC(aKey); bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
aValue.kind() != JS::TraceKind::String; if (!keyMightNeedMarking && !valueMightNeedMarking) { return;
}
if (!JS::IsCCTraceKind(aKey.kind())) {
aKey = nullptr;
}
// NB: This is only used to initialize the participant in // CycleCollectedJSRuntime. It should never be used directly. static JSGCThingParticipant sGCThingCycleCollectorGlobal;
void TraversalTracer::onChild(JS::GCCellPtr aThing, constchar* name) { // Checking strings and symbols for being gray is rather slow, and we don't // need either of them for the cycle collector. if (aThing.is<JSString>() || aThing.is<JS::Symbol>()) { return;
}
// Don't traverse non-gray objects, unless we want all traces. if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) { return;
}
/* * This function needs to be careful to avoid stack overflow. Normally, when * IsCCTraceKind is true, the recursion terminates immediately as we just add * |thing| to the CC graph. So overflow is only possible when there are long * or cyclic chains of non-IsCCTraceKind GC things. Places where this can * occur use special APIs to handle such chains iteratively.
*/ if (JS::IsCCTraceKind(aThing.kind())) { if (MOZ_UNLIKELY(mCb.WantDebugInfo())) { char buffer[200];
context().getEdgeName(name, buffer, sizeof(buffer));
mCb.NoteNextEdgeName(buffer);
}
mCb.NoteJSChild(aThing); return;
}
// Allow re-use of this tracer inside trace callback.
JS::AutoClearTracingContext actc(this);
if (aThing.is<js::Shape>()) { // The maximum depth of traversal when tracing a Shape is unbounded, due to // the parent pointers on the shape.
JS_TraceShapeCycleCollectorChildren(this, aThing);
} else {
JS::TraceChildren(this, aThing);
}
}
/* * The cycle collection participant for a Zone is intended to produce the same * results as if all of the gray GCthings in a zone were merged into a single * node, except for self-edges. This avoids the overhead of representing all of * the GCthings in the zone in the cycle collector graph, which should be much * faster if many of the GCthings in the zone are gray. * * Zone merging should not always be used, because it is a conservative * approximation of the true cycle collector graph that can incorrectly identify * some garbage objects as being live. For instance, consider two cycles that * pass through a zone, where one is garbage and the other is live. If we merge * the entire zone, the cycle collector will think that both are alive. * * We don't have to worry about losing track of a garbage cycle, because any * such garbage cycle incorrectly identified as live must contain at least one * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph. * (This is in contrast to pure C++ garbage cycles, which must always be * properly identified, because we clear the purple buffer during every CC, * which may contain the last reference to a garbage cycle.)
*/
// NB: This is only used to initialize the participant in // CycleCollectedJSRuntime. It should never be used directly. staticconst JSZoneParticipant sJSZoneCycleCollectorGlobal;
void JSHolderMap::EntryVectorIter::Settle() { if (Done()) { return;
}
Entry* entry = &mIter.Get();
// If the entry has been cleared, remove it and shrink the vector. if (!entry->mHolder && !mHolderMap.RemoveEntry(mVector, entry)) { // We removed the last entry, so reset the iterator to an empty one.
mIter = EntryVector().Iter();
MOZ_ASSERT(Done());
}
}
// Populate vector of zones to iterate after the any-zone holders. for (auto i = aMap.mPerZoneJSHolders.iter(); !i.done(); i.next()) {
JS::Zone* zone = i.get().key(); if (aWhich == AllHolders || JS::NeedGrayRootsForZone(i.get().key())) {
MOZ_ALWAYS_TRUE(mZones.append(zone));
}
}
Settle();
}
void JSHolderMap::Iter::Settle() { while (mIter.Done()) { if (mZone && mIter.Vector().IsEmpty()) {
mHolderMap.mPerZoneJSHolders.remove(mZone);
}
// Remove all dead entries from the end of the vector. while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) {
aJSHolders.PopLast();
}
// Swap the element we want to remove with the last one and update the hash // table.
Entry* lastEntry = &aJSHolders.GetLast(); if (aEntry != lastEntry) {
MOZ_ASSERT(lastEntry->mHolder);
*aEntry = *lastEntry;
MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder));
MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry));
}
aJSHolders.PopLast();
// Return whether aEntry is still in the vector. return aEntry != lastEntry;
}
size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = 0;
// We're deliberately not measuring anything hanging off the entries in // mJSHolders.
n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf);
n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf); for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) {
n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf);
}
// There should not be any roots left to trace at this point. Ensure any that // remain are flagged as leaks. #ifdef NS_BUILD_REFCNT_LOGGING
JSLeakTracer tracer(Runtime());
TraceNativeBlackRoots(&tracer);
TraceAllNativeGrayRoots(&tracer); #endif
if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) { // Nothing else to do! return;
}
// XXX This test does seem fragile, we should probably allowlist classes // that do hold a strong reference, but that might not be possible. if (aClasp->slot0IsISupports()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetObjectISupports(obj)");
aCb.NoteXPCOMChild(JS::GetObjectISupports<nsISupports>(obj)); return;
}
const DOMJSClass* domClass = GetDOMClass(aClasp); if (domClass) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)"); // It's possible that our object is an unforgeable holder object, in // which case it doesn't actually have a C++ DOM object associated with // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in // that case, since NoteXPCOMChild/NoteNativeChild are null-safe. if (domClass->mDOMObjectIsISupports) {
aCb.NoteXPCOMChild(
UnwrapPossiblyNotInitializedDOMObject<nsISupports>(obj));
} elseif (domClass->mParticipant) {
aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(obj),
domClass->mParticipant);
} return;
}
if (IsRemoteObjectProxy(obj)) { auto handler = static_cast<const RemoteObjectProxyBase*>(js::GetProxyHandler(obj)); return handler->NoteChildren(obj, aCb);
}
JS::Value value = js::MaybeGetScriptPrivate(obj); if (!value.isUndefined()) {
aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate()));
}
}
if (aTs == TRAVERSE_FULL) {
DescribeGCThing(!isMarkedGray, aThing, aCb);
}
// If this object is alive, then all of its children are alive. For JS // objects, the black-gray invariant ensures the children are also marked // black. For C++ objects, the ref count from this object will keep them // alive. Thus we don't need to trace our children, unless we are debugging // using WantAllTraces. if (!isMarkedGray && !aCb.WantAllTraces()) { return;
}
if (aTs == TRAVERSE_FULL) {
NoteGCThingJSChildren(aThing, aCb);
}
void CycleCollectedJSRuntime::TraverseZone(
JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) { /* * We treat the zone as being gray. We handle non-gray GCthings in the * zone by not reporting their children to the CC. The black-gray invariant * ensures that any JS children will also be non-gray, and thus don't need to * be added to the graph. For C++ children, not representing the edge from the * non-gray JS GCthings to the C++ object will keep the child alive. * * We don't allow zone merging in a WantAllTraces CC, because then these * assumptions don't hold.
*/
aCb.DescribeGCedNode(false, "JS Zone");
/* * Every JS child of everything in the zone is either in the zone * or is a cross-compartment wrapper. In the former case, we don't need to * represent these edges in the CC graph because JS objects are not ref * counted. In the latter case, the JS engine keeps a map of these wrappers, * which we iterate over. Edges between compartments in the same zone will add * unnecessary loop edges to the graph (bug 842137).
*/
TraversalTracer trc(mJSRuntime, aCb);
js::TraceGrayWrapperTargets(&trc, aZone);
/* * To find C++ children of things in the zone, we scan every JS Object in * the zone. Only JS Objects can have C++ children.
*/
TraverseObjectShimClosure closure = {aCb, this};
js::IterateGrayObjects(aZone, TraverseObjectShim, &closure);
}
void CycleCollectedJSRuntime::TraverseNativeRoots(
nsCycleCollectionNoteRootCallback& aCb) { // NB: This is here just to preserve the existing XPConnect order. I doubt it // would hurt to do this after the JS holders.
TraverseAdditionalNativeRoots(aCb);
// Mark these roots as gray so the CC can walk them later.
JSHolderMap::WhichHolders which = JSHolderMap::AllHolders;
// Only trace holders in collecting zones when marking, except if we are // collecting the atoms zone since any holder may point into that zone. if (aTracer->isMarkingTracer() &&
!JS::AtomsZoneIsCollecting(self->Runtime())) {
which = JSHolderMap::HoldersRequiredForGrayMarking;
}
struct GCMajorMarker : public BaseMarkerType<GCMajorMarker> { static constexpr constchar* Name = "GCMajor"; static constexpr constchar* Description = "Summary data for an entire major GC, encompassing a set of " "incremental slices. The main thread is not blocked for the " "entire major GC interval, only for the individual slices.";
using MS = MarkerSchema; static constexpr MS::PayloadField PayloadFields[] = {
{"timings", MS::InputType::CString, "GC timings"}};
TimeStamp now = TimeStamp::Now(); if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) {
self->mLatestNurseryCollectionStart = now;
} elseif (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) {
PerfStats::RecordMeasurement(PerfStats::Metric::MinorGC,
now - self->mLatestNurseryCollectionStart);
}
if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END &&
profiler_thread_is_being_profiled_for_markers()) { struct GCMinorMarker { static constexpr mozilla::Span<constchar> MarkerTypeName() { return mozilla::MakeStringSpan("GCMinor");
} staticvoid StreamJSONMarkerData(
mozilla::baseprofiler::SpliceableJSONWriter& aWriter, const mozilla::ProfilerString8View& aTimingJSON) { if (aTimingJSON.Length() != 0) {
aWriter.SplicedJSONProperty("nursery", aTimingJSON);
} else {
aWriter.NullProperty("nursery");
}
} static mozilla::MarkerSchema MarkerTypeDisplay() { using MS = mozilla::MarkerSchema;
MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
MS::Location::TimelineMemory};
schema.AddStaticLabelValue( "Description", "A minor GC (aka nursery collection) to clear out the buffer used " "for recent allocations and move surviving data to the tenured " "(long-lived) heap."); // No display instructions here, there is special handling in the // front-end. return schema;
}
};
// aMemory is stack allocated memory to contain our RAII object. This allows // for us to avoid allocations on the heap during this callback. returnnew (aMemory) dom::AutoYieldJSThreadExecution;
}
// A tracer that checks that a JS holder only holds JS GC things in a single // JS::Zone. struct CheckZoneTracer : public TraceCallbacks { constchar* mClassName; mutable JS::Zone* mZone;
void checkZone(JS::Zone* aZone, constchar* aName) const { if (JS::IsAtomsZone(aZone)) { // Any holder may contain pointers into the atoms zone. return;
}
if (!mZone) {
mZone = aZone; return;
}
if (aZone == mZone) { return;
}
// Most JS holders only contain pointers to GC things in a single zone. We // group holders by referent zone where possible, allowing us to improve GC // performance by only tracing holders for zones that are being collected. // // Additionally, pointers from any holder into the atoms zone are allowed // since all holders are traced when we collect the atoms zone. // // If you added a holder that has pointers into multiple zones do not // use NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
MOZ_CRASH_UNSAFE_PRINTF( "JS holder %s contains pointers to GC things in more than one zone (" "found in %s)\n",
mClassName, aName);
}
bool CycleCollectedJSRuntime::TraceNativeGrayRoots(
JSTracer* aTracer, JSHolderMap::WhichHolders aWhich,
JS::SliceBudget& aBudget) { if (!mHolderIter) { // NB: This is here just to preserve the existing XPConnect order. I doubt // it would hurt to do this after the JS holders.
TraceAdditionalNativeGrayRoots(aTracer);
mHolderIter.emplace(mJSHolders, aWhich);
aBudget.forceCheck();
} else { // Holders may have been removed between slices, so we may need to update // the iterator.
mHolderIter->UpdateForRemovals();
}
void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) {
nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder); if (tracer) { // Bug 1531951: The analysis can't see through the virtual call but we know // that the ClearJSHolder tracer will never GC.
JS::AutoSuppressGCAnalysis nogc;
tracer->Trace(aHolder, ClearJSHolder(), nullptr);
}
}
void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const {
MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime), "Don't call FixWeakMappingGrayBits during a GC.");
FixWeakMappingGrayBitsTracer fixer(mJSRuntime);
fixer.FixAll();
}
void CycleCollectedJSRuntime::CheckGrayBits() const {
MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime), "Don't call CheckGrayBits during a GC.");
#ifndef ANDROID // Bug 1346874 - The gray state check is expensive. Android tests are already // slow enough that this check can easily push them over the threshold to a // timeout.
mozilla::AutoRestore<bool> ar(mReleasing);
mReleasing = true;
MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0, "We should have at least ReleaseSliceNow to run");
MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(), "No more finalizers to run?");
TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
TimeStamp started = aLimited ? TimeStamp::Now() : TimeStamp(); bool timeout = false; do { const DeferredFinalizeFunctionHolder& function =
mDeferredFinalizeFunctions[mFinalizeFunctionToRun]; if (aLimited) { bool done = false; while (!timeout && !done) { /* * We don't want to read the clock too often, so we try to * release slices of 100 items.
*/
done = function.run(100, function.data);
timeout = TimeStamp::Now() - started >= sliceTime;
} if (done) {
++mFinalizeFunctionToRun;
} if (timeout) { break;
}
} else { while (!function.run(UINT32_MAX, function.data));
++mFinalizeFunctionToRun;
}
} while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
}
if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
mDeferredFinalizeFunctions.Clear();
CycleCollectedJSRuntime* runtime = mRuntime;
mRuntime = nullptr; // NB: This may delete this!
runtime->mFinalizeRunnable = nullptr;
}
}
NS_IMETHODIMP
IncrementalFinalizeRunnable::Run() { if (!mDeferredFinalizeFunctions.Length()) { /* These items were already processed synchronously in JSGC_END. */
MOZ_ASSERT(!mRuntime); return NS_OK;
}
MOZ_ASSERT(mRuntime->mFinalizeRunnable == this); auto timerId = glean::cycle_collector::deferred_finalize_async.Start();
ReleaseNow(true);
if (mDeferredFinalizeFunctions.Length()) {
nsresult rv = NS_DispatchToCurrentThread(this); if (NS_FAILED(rv)) {
ReleaseNow(false);
}
} else {
MOZ_ASSERT(!mRuntime);
}
void CycleCollectedJSRuntime::FinalizeDeferredThings(
DeferredFinalizeType aType) { // If mFinalizeRunnable isn't null, we didn't finalize everything from the // previous GC. if (mFinalizeRunnable) { if (aType == FinalizeLater) { // We need to defer all finalization until we return to the event loop, // so leave things alone. Any new objects to be finalized from the current // GC will be handled by the existing mFinalizeRunnable. return;
}
MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeNow); // If we're finalizing incrementally, we don't want finalizers to build up, // so try to finish them off now. // If we're finalizing synchronously, also go ahead and clear them out, // so we make sure as much as possible is freed.
mFinalizeRunnable->ReleaseNow(false); if (mFinalizeRunnable) { // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and // we need to just continue processing it. return;
}
}
// If there's nothing to finalize, don't create a new runnable. if (mDeferredFinalizerTable.Count() == 0) { return;
}
mFinalizeRunnable = new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable);
// Everything should be gone now.
MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus,
JS::GCReason aReason) { switch (aStatus) { case JSGC_BEGIN:
MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
nsCycleCollector_prepareForGarbageCollection();
PrepareWaitingZonesForGC(); break; case JSGC_END: {
MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); if (mOutOfMemoryState == OOMState::Reported) {
AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
} if (mLargeAllocationFailureState == OOMState::Reported) {
AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState,
OOMState::Recovered);
}
DeferredFinalizeType finalizeType; if (JS_IsExceptionPending(aContext)) { // There is a pending exception. The finalizers are not set up to run // in that state, so don't run the finalizer until we've returned to the // event loop.
finalizeType = FinalizeLater;
} elseif (JS::InternalGCReason(aReason)) { if (aReason == JS::GCReason::DESTROY_RUNTIME) { // We're shutting down, so we need to destroy things immediately.
finalizeType = FinalizeNow;
} else { // We may be in the middle of running some code that the JIT has // assumed can't have certain kinds of side effects. Finalizers can do // all sorts of things, such as run JS, so we want to run them later, // after we've returned to the event loop.
finalizeType = FinalizeLater;
}
} elseif (JS::WasIncrementalGC(mJSRuntime)) { // The GC was incremental, so we probably care about pauses. Try to // break up finalization, but it is okay if we do some now.
finalizeType = FinalizeIncrementally;
} else { // If we're running a synchronous GC, we probably want to free things as // quickly as possible. This can happen during testing or if memory is // low.
finalizeType = FinalizeNow;
}
FinalizeDeferredThings(finalizeType);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.80Bemerkung:
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.