• mhahnenberg@apple.com's avatar
    De-virtualize destructors · c58d54d7
    mhahnenberg@apple.com authored
    https://bugs.webkit.org/show_bug.cgi?id=74331
    
    Reviewed by Geoffrey Garen.
    
    .: 
    
    * Source/autotools/symbols.filter: Removed symbol no longer present.
    
    Source/JavaScriptCore: 
    
    This is a megapatch which frees us from the chains of virtual destructors.
    
    In order to remove the virtual destructors, which are the last of the virtual 
    functions, from the JSCell hierarchy, we need to add the ClassInfo pointer to 
    the cell rather than to the structure because in order to be able to lazily call 
    the static destroy() functions that will replace the virtual destructors, we 
    need to be able to access the ClassInfo without the danger of the object's 
    Structure being collected before the object itself.
    
    After adding the ClassInfo to the cell, we can then begin to remove our use 
    of vptrs for optimizations within the JIT and the GC.  When we have removed 
    all of the stored vptrs from JSGlobalData, we can then also remove all of 
    the related VPtrStealingHack code.
    
    The replacement for virtual destructors will be to add a static destroy function 
    pointer to the MethodTable stored in ClassInfo.  Any subclass of JSCell that has 
    a non-trivial destructor will require its own static destroy function to static 
    call its corresponding destructor, which will now be non-virtual.  In future 
    patches we will slowly move away from destructors altogether as we make more and 
    more objects backed by GC memory rather than malloc-ed memory.  The GC will now 
    call the static destroy method rather than the virtual destructor.
    
    As we go through the hierarchy and add static destroy functions to classes, 
    we will also add a new assert, ASSERT_HAS_TRIVIAL_DESTRUCTOR, to those classes 
    to which it applies.  The future goal is to eventually have every class have that assert.
    
    * API/JSCallbackConstructor.cpp:
    (JSC::JSCallbackConstructor::destroy): Add a destroy function to statically call 
    ~JSCallbackConstructor because it has some extra destruction logic.
    * API/JSCallbackConstructor.h:
    * API/JSCallbackFunction.cpp: Add trivial destructor assert for JSCallbackFunction.
    * API/JSCallbackObject.cpp: Add a destroy function to statically call ~JSCallbackObject 
    because it has a member OwnPtr that needs destruction.
    (JSC::::destroy):
    * API/JSCallbackObject.h:
    * JavaScriptCore.exp: Add/remove necessary symbols for JSC.
    * JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def: Same for Windows symbols.
    * debugger/DebuggerActivation.cpp: DebuggerActivation, for some strange reason, didn't 
    have its own ClassInfo despite the fact that it overrides a number of MethodTable 
    methods.  Added the ClassInfo, along with an assertion that its destructor is trivial.
    * debugger/DebuggerActivation.h:
    * dfg/DFGOperations.cpp: Remove global data first argument to isJSArray, isJSByteArray, 
    isJSString, as it is no longer necessary.
    (JSC::DFG::putByVal):
    * dfg/DFGRepatch.cpp:  Ditto.  Also remove uses of jsArrayVPtr in favor of using the 
    JSArray ClassInfo pointer.
    (JSC::DFG::tryCacheGetByID):
    * dfg/DFGSpeculativeJIT.cpp:  Replace uses of the old vptrs with new ClassInfo 
    comparisons since we don't have vptrs anymore.
    (JSC::DFG::SpeculativeJIT::compilePeepHoleObjectEquality):
    (JSC::DFG::SpeculativeJIT::compilePeepHoleBranch):
    (JSC::DFG::SpeculativeJIT::checkArgumentTypes):
    (JSC::DFG::SpeculativeJIT::compilePutByValForByteArray):
    (JSC::DFG::SpeculativeJIT::compileGetTypedArrayLength):
    (JSC::DFG::SpeculativeJIT::compilePutByValForIntTypedArray):
    (JSC::DFG::SpeculativeJIT::compilePutByValForFloatTypedArray):
    (JSC::DFG::SpeculativeJIT::compare):
    (JSC::DFG::SpeculativeJIT::compileStrictEq):
    (JSC::DFG::SpeculativeJIT::compileGetIndexedPropertyStorage):
    * dfg/DFGSpeculativeJIT.h: Ditto.
    (JSC::DFG::SpeculativeJIT::emitAllocateJSFinalObject):
    * dfg/DFGSpeculativeJIT32_64.cpp: Ditto.
    (JSC::DFG::SpeculativeJIT::compileObjectEquality):
    (JSC::DFG::SpeculativeJIT::compileObjectOrOtherLogicalNot):
    (JSC::DFG::SpeculativeJIT::compileLogicalNot):
    (JSC::DFG::SpeculativeJIT::emitObjectOrOtherBranch):
    (JSC::DFG::SpeculativeJIT::emitBranch):
    (JSC::DFG::SpeculativeJIT::compile):
    * dfg/DFGSpeculativeJIT64.cpp: Ditto.
    (JSC::DFG::SpeculativeJIT::compileObjectEquality):
    (JSC::DFG::SpeculativeJIT::compileObjectOrOtherLogicalNot):
    (JSC::DFG::SpeculativeJIT::compileLogicalNot):
    (JSC::DFG::SpeculativeJIT::emitObjectOrOtherBranch):
    (JSC::DFG::SpeculativeJIT::emitBranch):
    (JSC::DFG::SpeculativeJIT::compile):
    * heap/Heap.cpp: Remove all uses of vptrs in GC optimizations and replace them with 
    ClassInfo comparisons.
    (JSC::Heap::Heap):
    * heap/MarkStack.cpp: Ditto.
    (JSC::MarkStackThreadSharedData::markingThreadMain):
    (JSC::visitChildren):
    (JSC::SlotVisitor::drain):
    * heap/MarkStack.h: Ditto.
    (JSC::MarkStack::MarkStack):
    * heap/MarkedBlock.cpp: Ditto.
    (JSC::MarkedBlock::callDestructor):
    (JSC::MarkedBlock::specializedSweep):
    * heap/MarkedBlock.h: Ditto.
    * heap/SlotVisitor.h: Ditto.
    (JSC::SlotVisitor::SlotVisitor):
    * heap/VTableSpectrum.cpp: Now that we don't have vptrs, we can't count them.  
    We'll have to rename this class and make it use ClassInfo ptrs in a future patch.
    (JSC::VTableSpectrum::count):
    * interpreter/Interpreter.cpp: Remove all global data arguments from isJSArray, 
    etc. functions.
    (JSC::loadVarargs):
    (JSC::Interpreter::tryCacheGetByID):
    (JSC::Interpreter::privateExecute):
    * jit/JIT.h: Remove vptr argument from emitAllocateBasicJSObject 
    * jit/JITInlineMethods.h: Remove vptr planting, and add ClassInfo planting, 
    remove all vtable related code.
    (JSC::JIT::emitLoadCharacterString):
    (JSC::JIT::emitAllocateBasicJSObject):
    (JSC::JIT::emitAllocateJSFinalObject):
    (JSC::JIT::emitAllocateJSFunction):
    * jit/JITOpcodes.cpp: Replace vptr related branch code with corresponding ClassInfo.
    (JSC::JIT::privateCompileCTIMachineTrampolines):
    (JSC::JIT::emit_op_to_primitive):
    (JSC::JIT::emit_op_convert_this):
    * jit/JITOpcodes32_64.cpp: Ditto.
    (JSC::JIT::privateCompileCTIMachineTrampolines):
    (JSC::JIT::emit_op_to_primitive):
    (JSC::JIT::emitSlow_op_eq):
    (JSC::JIT::emitSlow_op_neq):
    (JSC::JIT::compileOpStrictEq):
    (JSC::JIT::emit_op_convert_this):
    * jit/JITPropertyAccess.cpp: Ditto.
    (JSC::JIT::stringGetByValStubGenerator):
    (JSC::JIT::emit_op_get_by_val):
    (JSC::JIT::emitSlow_op_get_by_val):
    (JSC::JIT::emit_op_put_by_val):
    (JSC::JIT::privateCompilePutByIdTransition):
    (JSC::JIT::privateCompilePatchGetArrayLength):
    * jit/JITPropertyAccess32_64.cpp: Ditto.
    (JSC::JIT::stringGetByValStubGenerator):
    (JSC::JIT::emit_op_get_by_val):
    (JSC::JIT::emitSlow_op_get_by_val):
    (JSC::JIT::emit_op_put_by_val):
    (JSC::JIT::privateCompilePatchGetArrayLength):
    * jit/JITStubs.cpp: Remove global data argument from isJSString, etc.
    (JSC::JITThunks::tryCacheGetByID):
    (JSC::DEFINE_STUB_FUNCTION):
    * jit/SpecializedThunkJIT.h: Replace vptr related stuff with ClassInfo stuff.
    (JSC::SpecializedThunkJIT::loadJSStringArgument):
    * runtime/ArrayConstructor.cpp: Add trivial destructor assert.
    * runtime/ArrayPrototype.cpp: Remove global data argument from isJSArray.
    (JSC::arrayProtoFuncToString):
    (JSC::arrayProtoFuncJoin):
    (JSC::arrayProtoFuncPop):
    (JSC::arrayProtoFuncPush):
    (JSC::arrayProtoFuncShift):
    (JSC::arrayProtoFuncSplice):
    (JSC::arrayProtoFuncUnShift):
    (JSC::arrayProtoFuncFilter):
    (JSC::arrayProtoFuncMap):
    (JSC::arrayProtoFuncEvery):
    (JSC::arrayProtoFuncForEach):
    (JSC::arrayProtoFuncSome):
    (JSC::arrayProtoFuncReduce):
    (JSC::arrayProtoFuncReduceRight):
    * runtime/BooleanConstructor.cpp: Add trivial destructor assert.
    * runtime/BooleanObject.cpp: Ditto.
    * runtime/BooleanPrototype.cpp: Ditto.
    * runtime/ClassInfo.h: Add destroy function pointer to MethodTable.
    * runtime/DateConstructor.cpp: Add trivial destructor assert.
    * runtime/DateInstance.cpp: Add destroy function for DateInstance because it has a RefPtr 
    that needs destruction.
    (JSC::DateInstance::destroy):
    * runtime/DateInstance.h:
    * runtime/Error.cpp: Ditto (because of UString member).
    (JSC::StrictModeTypeErrorFunction::destroy):
    * runtime/Error.h:
    * runtime/ErrorConstructor.cpp: Add trivial destructor assert.
    * runtime/ErrorInstance.cpp: Ditto.
    * runtime/ExceptionHelpers.cpp: Ditto.
    * runtime/Executable.cpp: Add destroy functions for ExecutableBase and subclasses.
    (JSC::ExecutableBase::destroy):
    (JSC::NativeExecutable::destroy):
    (JSC::ScriptExecutable::destroy):
    (JSC::EvalExecutable::destroy):
    (JSC::ProgramExecutable::destroy):
    (JSC::FunctionExecutable::destroy):
    * runtime/Executable.h:
    * runtime/FunctionConstructor.cpp: Add trivial destructor assert.
    * runtime/FunctionPrototype.cpp: Ditto. Also remove global data first arg from isJSArray.
    (JSC::functionProtoFuncApply):
    * runtime/GetterSetter.cpp: Ditto.
    * runtime/InitializeThreading.cpp: Remove call to JSGlobalData::storeVPtrs since it no 
    longer exists.
    (JSC::initializeThreadingOnce):
    * runtime/InternalFunction.cpp: Remove vtableAnchor function, add trivial destructor assert, 
    remove first arg from isJSString.
    (JSC::InternalFunction::displayName):
    * runtime/InternalFunction.h: Remove VPtrStealingHack.
    * runtime/JSAPIValueWrapper.cpp: Add trivial destructor assert.
    * runtime/JSArray.cpp: Add static destroy to call ~JSArray.  Replace vptr checks in 
    destructor with ClassInfo checks.
    (JSC::JSArray::~JSArray):
    (JSC::JSArray::destroy):
    * runtime/JSArray.h: Remove VPtrStealingHack.  Remove globalData argument from isJSArray 
    and change them to check the ClassInfo rather than the vptrs.
    (JSC::isJSArray):
    * runtime/JSBoundFunction.cpp: Add trival destructor assert. Remove first arg from isJSArray.
    (JSC::boundFunctionCall):
    (JSC::boundFunctionConstruct):
    * runtime/JSByteArray.cpp: Add static destroy function, replace vptr checks with ClassInfo checks.
    (JSC::JSByteArray::~JSByteArray):
    (JSC::JSByteArray::destroy):
    * runtime/JSByteArray.h: Remove VPtrStealingHack code.
    (JSC::isJSByteArray):
    * runtime/JSCell.cpp: Add trivial destructor assert.  Add static destroy function.
    (JSC::JSCell::destroy):
    * runtime/JSCell.h: Remove VPtrStealingHack code.  Add function for returning the offset 
    of the ClassInfo pointer in the object for use by the JIT.  Add the ClassInfo pointer to 
    the JSCell itself, and grab it from the Structure.  Remove the vptr and setVPtr functions, 
    as they are no longer used.  Add a validatedClassInfo function to JSCell for any clients 
    that want to verify, while in Debug mode, that the ClassInfo contained in the cell is the 
    same one as that contained in the Structure.  This isn't used too often, because most of 
    the places where we compare the ClassInfo to things can be called during destruction.  
    Since the Structure is unreliable during the phase when destructors are being called, 
    we can't call validatedClassInfo.
    (JSC::JSCell::classInfoOffset):
    (JSC::JSCell::structure):
    (JSC::JSCell::classInfo):
    * runtime/JSFunction.cpp: Remove VPtrStealingHack code.  Add static destroy, remove vtableAnchor, 
    remove first arg from call to isJSString.
    (JSC::JSFunction::destroy):
    (JSC::JSFunction::displayName):
    * runtime/JSFunction.h: 
    * runtime/JSGlobalData.cpp: Remove all VPtr stealing code and storage, including storeVPtrs, 
    as these vptrs are no longer needed in the codebase.
    * runtime/JSGlobalData.h:
    (JSC::TypedArrayDescriptor::TypedArrayDescriptor): Changed the TypedArrayDescriptor to use 
    ClassInfo rather than the vptr.
    * runtime/JSGlobalObject.cpp: Add static destroy function.
    (JSC::JSGlobalObject::destroy):
    * runtime/JSGlobalObject.h:
    * runtime/JSGlobalThis.cpp: Add trivial destructor assert.
    * runtime/JSNotAnObject.cpp: Ditto.
    * runtime/JSONObject.cpp: Ditto. Remove first arg from isJSArray calls.
    (JSC::Stringifier::Holder::appendNextProperty):
    (JSC::Walker::walk):
    * runtime/JSObject.cpp: 
    (JSC::JSFinalObject::destroy):
    (JSC::JSNonFinalObject::destroy):
    (JSC::JSObject::destroy):
    * runtime/JSObject.h: Add trivial destructor assert for JSObject, remove vtableAnchor 
    from JSNonFinalObject and JSFinalObject, add static destroy for JSFinalObject and 
    JSNonFinalObject, add isJSFinalObject utility function similar to isJSArray, remove all VPtrStealingHack code.
    (JSC::JSObject::finishCreation):
    (JSC::JSNonFinalObject::finishCreation):
    (JSC::JSFinalObject::finishCreation):
    (JSC::isJSFinalObject):
    * runtime/JSPropertyNameIterator.cpp: Add static destroy.
    (JSC::JSPropertyNameIterator::destroy):
    * runtime/JSPropertyNameIterator.h:
    * runtime/JSStaticScopeObject.cpp: Ditto.
    (JSC::JSStaticScopeObject::destroy):
    * runtime/JSStaticScopeObject.h: Ditto. 
    * runtime/JSString.cpp:
    (JSC::JSString::destroy):
    * runtime/JSString.h: Ditto. Remove VPtrStealingHack code. Also remove fixupVPtr code, 
    since we no longer need to fixup vptrs.
    (JSC::jsSingleCharacterString):
    (JSC::jsSingleCharacterSubstring):
    (JSC::jsNontrivialString):
    (JSC::jsString):
    (JSC::jsSubstring8):
    (JSC::jsSubstring):
    (JSC::jsOwnedString):
    (JSC::jsStringBuilder):
    (JSC::isJSString):
    * runtime/JSVariableObject.cpp: 
    (JSC::JSVariableObject::destroy):
    * runtime/JSVariableObject.h: Ditto.
    * runtime/JSWrapperObject.cpp:
    * runtime/JSWrapperObject.h: Add trivial destructor assert.
    * runtime/MathObject.cpp: Ditto.
    * runtime/NativeErrorConstructor.cpp: Ditto.
    * runtime/NumberConstructor.cpp: Ditto.
    * runtime/NumberObject.cpp: Ditto.
    * runtime/NumberPrototype.cpp: Ditto.
    * runtime/ObjectConstructor.cpp: Ditto.
    * runtime/ObjectPrototype.cpp: Ditto.
    * runtime/Operations.h: Remove calls to fixupVPtr, remove first arg to isJSString.
    (JSC::jsString):
    (JSC::jsLess):
    (JSC::jsLessEq):
    * runtime/RegExp.cpp: Add static destroy.
    (JSC::RegExp::destroy):
    * runtime/RegExp.h:
    * runtime/RegExpConstructor.cpp: Add static destroy for RegExpConstructor and RegExpMatchesArray.
    (JSC::RegExpConstructor::destroy):
    (JSC::RegExpMatchesArray::destroy):
    * runtime/RegExpConstructor.h:
    * runtime/RegExpMatchesArray.h:
    * runtime/RegExpObject.cpp: Add static destroy.
    (JSC::RegExpObject::destroy):
    * runtime/RegExpObject.h:
    * runtime/ScopeChain.cpp: Add trivial destructor assert.
    * runtime/ScopeChain.h:
    * runtime/StrictEvalActivation.cpp: Ditto.
    * runtime/StringConstructor.cpp:
    * runtime/StringObject.cpp: Ditto. Remove vtableAnchor.
    * runtime/StringObject.h:
    * runtime/StringPrototype.cpp: Ditto.
    * runtime/Structure.cpp: Add static destroy.
    (JSC::Structure::destroy):
    * runtime/Structure.h: Move JSCell::finishCreation and JSCell constructor into Structure.h 
    because they need to have the full Structure type to access the ClassInfo to store in the JSCell.
    (JSC::JSCell::setStructure):
    (JSC::JSCell::validatedClassInfo):
    (JSC::JSCell::JSCell):
    (JSC::JSCell::finishCreation):
    * runtime/StructureChain.cpp: Add static destroy.
    (JSC::StructureChain::destroy):
    * runtime/StructureChain.h:
    * wtf/Assertions.h: Add new assertion ASSERT_HAS_TRIVIAL_DESTRUCTOR, which uses clangs 
    ability to tell us when a class has a trivial destructor. We will use this assert 
    more in future patches as we move toward having all JSC objects backed by GC memory, 
    which means moving away from using destructors/finalizers.
    
    Source/JavaScriptGlue: 
    
    * UserObjectImp.cpp: Add static destroy function.
    (UserObjectImp::destroy):
    * UserObjectImp.h:
    
    Source/WebCore: 
    
    No new tests.
    
    Doing everything here that was done to the JSCell hierarchy in JavaScriptCore. 
    See the ChangeLog for this commit for a more in-depth description.
    
    * WebCore.exp.in: Add/remove symbols.
    * bindings/js/JSCanvasRenderingContext2DCustom.cpp: Remove first arg from isJSArray call.
    (WebCore::JSCanvasRenderingContext2D::setWebkitLineDash):
    * bindings/js/JSDOMBinding.cpp: Add trival destructor assert for DOMConstructorObject 
    and DOMConstructorWithDocument.
    * bindings/js/JSDOMGlobalObject.cpp: Add static destroy.  Add implementation for 
    scriptExecutionContext that dispatches to different functions in subclasses 
    depending on our current ClassInfo.  We do this so that we can get rid of the 
    virtual-ness of scriptExecutionContext, because any virtual functions will throw 
    off the layout of the object and we'll crash at runtime.
    (WebCore::JSDOMGlobalObject::destroy):
    (WebCore::JSDOMGlobalObject::scriptExecutionContext):
    * bindings/js/JSDOMGlobalObject.h:
    * bindings/js/JSDOMWindowBase.cpp: Add static destroy.
    (WebCore::JSDOMWindowBase::destroy):
    * bindings/js/JSDOMWindowBase.h: De-virtualize scriptExecutionContext.
    * bindings/js/JSDOMWindowShell.cpp: Add static destroy.
    (WebCore::JSDOMWindowShell::destroy):
    * bindings/js/JSDOMWindowShell.h:
    * bindings/js/JSDOMWrapper.cpp: Add trivial destructor assert.
    * bindings/js/JSDOMWrapper.h: Add a ClassInfo to JSDOMWrapper since it now overrides 
    a MethodTable function. Remove vtableAnchor virtual function.
    * bindings/js/JSImageConstructor.cpp: Add trivial destructor assert.
    * bindings/js/JSNodeCustom.cpp: Change implementation of pushEventHandlerScope so that 
    it dispatches to the correct function depending on the 
    identity of the class as specified by the ClassInfo.  
    See JSDOMGlobalObject::scriptExecutionContext for explanation.
    (WebCore::JSNode::pushEventHandlerScope):
    * bindings/js/JSWebSocketCustom.cpp: Remove first arg to isJSArray call.
    (WebCore::JSWebSocketConstructor::constructJSWebSocket):
    * bindings/js/JSWorkerContextBase.cpp: Add static destroy.
    (WebCore::JSWorkerContextBase::destroy):
    * bindings/js/JSWorkerContextBase.h: 
    * bindings/js/ScriptValue.cpp: Remove first arg to isJSArray call.
    (WebCore::jsToInspectorValue): 
    * bindings/js/SerializedScriptValue.cpp: Ditto.
    (WebCore::CloneSerializer::isArray):
    (WebCore::CloneSerializer::getSparseIndex):
    * bindings/scripts/CodeGeneratorJS.pm:
    (GenerateHeader): Remove virtual-ness of any custom pushEventHandlerScope (see 
    JSNodeCustom::pushEventHandlerScope for explanation).  Remove virtual toBoolean 
    for anybody who masquerades as undefined, since our JSObject implementation handles 
    this based on the TypeInfo in the Structure. Add trivial destructor assert for any 
    class other than DOMWindow or WorkerContexts.
    (GenerateImplementation): Change ClassInfo definitions to use Base::s_info, since 
    typing the parent class more than once is duplication of information and increases 
    the likelihood of mistakes.  Pass ClassInfo to TypeArrayDescriptors instead of vptr. 
    (GenerateConstructorDefinition): Add trivial destructor assert for all generated constructors.
    * bridge/c/CRuntimeObject.cpp: Remove empty virtual destructor.
    * bridge/c/CRuntimeObject.h: 
    * bridge/jni/jsc/JavaRuntimeObject.cpp: Ditto.
    * bridge/jni/jsc/JavaRuntimeObject.h: 
    * bridge/objc/ObjCRuntimeObject.h: Ditto.
    * bridge/objc/ObjCRuntimeObject.mm:
    * bridge/objc/objc_runtime.h: Add static destroy for ObjcFallbackObjectImp. De-virtualize 
    toBoolean in the short term.  Need longer term fix.
    * bridge/objc/objc_runtime.mm:
    (JSC::Bindings::ObjcFallbackObjectImp::destroy):
    * bridge/qt/qt_runtime.cpp: Add static destroy to QtRuntimeMethod.
    (JSC::Bindings::QtRuntimeMethod::destroy):
    * bridge/qt/qt_runtime.h: De-virtualize ~QtRuntimeMethod.
    * bridge/runtime_array.cpp: De-virtualize destructor. Add static destroy.
    (JSC::RuntimeArray::destroy):
    * bridge/runtime_array.h:
    * bridge/runtime_method.cpp: Remove vtableAnchor. Add static destroy.
    (JSC::RuntimeMethod::destroy):
    * bridge/runtime_method.h:
    * bridge/runtime_object.cpp: Add static destroy.
    (JSC::Bindings::RuntimeObject::destroy):
    * bridge/runtime_object.h:
    
    Source/WebKit/mac: 
    
    * Plugins/Hosted/ProxyRuntimeObject.h: Remove empty virtual destructor.
    * Plugins/Hosted/ProxyRuntimeObject.mm:
    
    Source/WebKit2: 
    
    * WebProcess/Plugins/Netscape/JSNPMethod.cpp: Add trivial destructor assert.
    * WebProcess/Plugins/Netscape/JSNPObject.cpp: Add static destroy.
    (WebKit::JSNPObject::destroy):
    * WebProcess/Plugins/Netscape/JSNPObject.h:
    * win/WebKit2.def: Add/remove necessary symbols.
    * win/WebKit2CFLite.def: Ditto.
    
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@103083 268f45cc-cd09-0410-ab3c-d52691b4dbfc
    c58d54d7
StringConstructor.cpp 4.63 KB