static void describeUninitializedArgumentInCall(const CallEvent &Call, int ArgumentNumber, llvm::raw_svector_ostream &Os) { switch (Call.getKind()) { case CE_ObjCMessage: { const ObjCMethodCall &Msg = cast<ObjCMethodCall>(Call); switch (Msg.getMessageKind()) { case OCM_Message: Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1) << " argument in message expression is an uninitialized value"; return; case OCM_PropertyAccess: assert(Msg.isSetter() && "Getters have no args"); Os << "Argument for property setter is an uninitialized value"; return; case OCM_Subscript: if (Msg.isSetter() && (ArgumentNumber == 0)) Os << "Argument for subscript setter is an uninitialized value"; else Os << "Subscript index is an uninitialized value"; return; } llvm_unreachable("Unknown message kind."); } case CE_Block: Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1) << " block call argument is an uninitialized value"; return; default: Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1) << " function call argument is an uninitialized value"; return; } }
bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State) { assert(D); const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); const LocationContext *ParentOfCallee = CallerSFC; if (Call.getKind() == CE_Block && !cast<BlockCall>(Call).isConversionFromLambda()) { const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion(); assert(BR && "If we have the block definition we should have its region"); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); ParentOfCallee = BlockCtx->getBlockInvocationContext(CallerSFC, cast<BlockDecl>(D), BR); } // This may be NULL, but that's fine. const Expr *CallE = Call.getOriginExpr(); // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = CalleeADC->getStackFrame(ParentOfCallee, CallE, currBldrCtx->getBlock(), currStmtIdx); CallEnter Loc(CallE, CalleeSFC, CurLC); // Construct a new state which contains the mapping from actual to // formal arguments. State = State->enterStackFrame(Call, CalleeSFC); bool isNew; if (ExplodedNode *N = G.getNode(Loc, State, false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) Engine.getWorkList()->enqueue(N); } // If we decided to inline the call, the successor has been manually // added onto the work list so remove it from the node builder. Bldr.takeNodes(Pred); NumInlinedCalls++; Engine.FunctionSummaries->bumpNumTimesInlined(D); // Mark the decl as visited. if (VisitedCallees) VisitedCallees->insert(D); return true; }
/// Suppress the nullability warnings for some functions. void NullabilityChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { auto Decl = Call.getDecl(); if (!Decl) return; // ObjC Messages handles in a different callback. if (Call.getKind() == CE_ObjCMessage) return; const FunctionType *FuncType = Decl->getFunctionType(); if (!FuncType) return; QualType ReturnType = FuncType->getReturnType(); if (!ReturnType->isAnyPointerType()) return; ProgramStateRef State = C.getState(); if (State->get<PreconditionViolated>()) return; const MemRegion *Region = getTrackRegion(Call.getReturnValue()); if (!Region) return; // CG headers are misannotated. Do not warn for symbols that are the results // of CG calls. const SourceManager &SM = C.getSourceManager(); StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getLocStart())); if (llvm::sys::path::filename(FilePath).startswith("CG")) { State = State->set<NullabilityMap>(Region, Nullability::Contradicted); C.addTransition(State); return; } const NullabilityState *TrackedNullability = State->get<NullabilityMap>(Region); if (!TrackedNullability && getNullabilityAnnotation(ReturnType) == Nullability::Nullable) { State = State->set<NullabilityMap>(Region, Nullability::Nullable); C.addTransition(State); } }
static StringRef describeUninitializedArgumentInCall(const CallEvent &Call, bool IsFirstArgument) { switch (Call.getKind()) { case CE_ObjCMessage: { const ObjCMethodCall &Msg = cast<ObjCMethodCall>(Call); switch (Msg.getMessageKind()) { case OCM_Message: return "Argument in message expression is an uninitialized value"; case OCM_PropertyAccess: assert(Msg.isSetter() && "Getters have no args"); return "Argument for property setter is an uninitialized value"; case OCM_Subscript: if (Msg.isSetter() && IsFirstArgument) return "Argument for subscript setter is an uninitialized value"; return "Subscript index is an uninitialized value"; } llvm_unreachable("Unknown message kind."); } case CE_Block: return "Block call argument is an uninitialized value"; default: return "Function call argument is an uninitialized value"; } }
static CallInlinePolicy mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, AnalyzerOptions &Opts) { const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); switch (Call.getKind()) { case CE_Function: case CE_Block: break; case CE_CXXMember: case CE_CXXMemberOperator: if (!Opts.mayInlineCXXMemberFunction(CIMK_MemberFunctions)) return CIP_DisallowedAlways; break; case CE_CXXConstructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Constructors)) return CIP_DisallowedAlways; const CXXConstructorCall &Ctor = cast<CXXConstructorCall>(Call); // FIXME: We don't handle constructors or destructors for arrays properly. // Even once we do, we still need to be careful about implicitly-generated // initializers for array fields in default move/copy constructors. const MemRegion *Target = Ctor.getCXXThisVal().getAsRegion(); if (Target && isa<ElementRegion>(Target)) return CIP_DisallowedOnce; // FIXME: This is a hack. We don't use the correct region for a new // expression, so if we inline the constructor its result will just be // thrown away. This short-term hack is tracked in <rdar://problem/12180598> // and the longer-term possible fix is discussed in PR12014. const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr(); if (const Stmt *Parent = CurLC->getParentMap().getParent(CtorExpr)) if (isa<CXXNewExpr>(Parent)) return CIP_DisallowedOnce; // Inlining constructors requires including initializers in the CFG. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddInitializers && "No CFG initializers"); (void)ADC; // If the destructor is trivial, it's always safe to inline the constructor. if (Ctor.getDecl()->getParent()->hasTrivialDestructor()) break; // For other types, only inline constructors if destructor inlining is // also enabled. if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; // FIXME: This is a hack. We don't handle temporary destructors // right now, so we shouldn't inline their constructors. if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) if (!Target || !isa<DeclRegion>(Target)) return CIP_DisallowedOnce; break; } case CE_CXXDestructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; // Inlining destructors requires building the CFG correctly. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; const CXXDestructorCall &Dtor = cast<CXXDestructorCall>(Call); // FIXME: We don't handle constructors or destructors for arrays properly. const MemRegion *Target = Dtor.getCXXThisVal().getAsRegion(); if (Target && isa<ElementRegion>(Target)) return CIP_DisallowedOnce; break; } case CE_CXXAllocator: if (Opts.mayInlineCXXAllocator()) break; // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return CIP_DisallowedAlways; case CE_ObjCMessage: if (!Opts.mayInlineObjCMethod()) return CIP_DisallowedAlways; if (!(Opts.getIPAMode() == IPAK_DynamicDispatch || Opts.getIPAMode() == IPAK_DynamicDispatchBifurcate)) return CIP_DisallowedAlways; break; } return CIP_Allowed; }
ExprEngine::CallInlinePolicy ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, AnalyzerOptions &Opts, const ExprEngine::EvalCallOptions &CallOpts) { const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); switch (Call.getKind()) { case CE_Function: case CE_Block: break; case CE_CXXMember: case CE_CXXMemberOperator: if (!Opts.mayInlineCXXMemberFunction(CIMK_MemberFunctions)) return CIP_DisallowedAlways; break; case CE_CXXConstructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Constructors)) return CIP_DisallowedAlways; const CXXConstructorCall &Ctor = cast<CXXConstructorCall>(Call); const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr(); // FIXME: ParentMap is slow and ugly. The callee should provide the // necessary context. Ideally as part of the call event, or maybe as part of // location context. const Stmt *ParentExpr = CurLC->getParentMap().getParent(CtorExpr); if (ParentExpr && isa<CXXNewExpr>(ParentExpr) && !Opts.mayInlineCXXAllocator()) return CIP_DisallowedOnce; // FIXME: We don't handle constructors or destructors for arrays properly. // Even once we do, we still need to be careful about implicitly-generated // initializers for array fields in default move/copy constructors. // We still allow construction into ElementRegion targets when they don't // represent array elements. if (CallOpts.IsArrayConstructorOrDestructor) return CIP_DisallowedOnce; // Inlining constructors requires including initializers in the CFG. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddInitializers && "No CFG initializers"); (void)ADC; // If the destructor is trivial, it's always safe to inline the constructor. if (Ctor.getDecl()->getParent()->hasTrivialDestructor()) break; // For other types, only inline constructors if destructor inlining is // also enabled. if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; // FIXME: This is a hack. We don't handle temporary destructors // right now, so we shouldn't inline their constructors. if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) if (CallOpts.IsConstructorWithImproperlyModeledTargetRegion) return CIP_DisallowedOnce; break; } case CE_CXXDestructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; // Inlining destructors requires building the CFG correctly. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; // FIXME: We don't handle constructors or destructors for arrays properly. if (CallOpts.IsArrayConstructorOrDestructor) return CIP_DisallowedOnce; break; } case CE_CXXAllocator: if (Opts.mayInlineCXXAllocator()) break; // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return CIP_DisallowedAlways; case CE_ObjCMessage: if (!Opts.mayInlineObjCMethod()) return CIP_DisallowedAlways; if (!(Opts.getIPAMode() == IPAK_DynamicDispatch || Opts.getIPAMode() == IPAK_DynamicDispatchBifurcate)) return CIP_DisallowedAlways; break; } return CIP_Allowed; }
bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State) { assert(D); const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); const LocationContext *ParentOfCallee = 0; const AnalyzerOptions &Opts = getAnalysisManager().options; // FIXME: Refactor this check into a hypothetical CallEvent::canInline. switch (Call.getKind()) { case CE_Function: break; case CE_CXXMember: case CE_CXXMemberOperator: if (!Opts.mayInlineCXXMemberFunction(CIMK_MemberFunctions)) return false; break; case CE_CXXConstructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Constructors)) return false; const CXXConstructorCall &Ctor = cast<CXXConstructorCall>(Call); // FIXME: We don't handle constructors or destructors for arrays properly. const MemRegion *Target = Ctor.getCXXThisVal().getAsRegion(); if (Target && isa<ElementRegion>(Target)) return false; // FIXME: This is a hack. We don't use the correct region for a new // expression, so if we inline the constructor its result will just be // thrown away. This short-term hack is tracked in <rdar://problem/12180598> // and the longer-term possible fix is discussed in PR12014. const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr(); if (const Stmt *Parent = CurLC->getParentMap().getParent(CtorExpr)) if (isa<CXXNewExpr>(Parent)) return false; // Inlining constructors requires including initializers in the CFG. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddInitializers && "No CFG initializers"); (void)ADC; // If the destructor is trivial, it's always safe to inline the constructor. if (Ctor.getDecl()->getParent()->hasTrivialDestructor()) break; // For other types, only inline constructors if destructor inlining is // also enabled. if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return false; // FIXME: This is a hack. We don't handle temporary destructors // right now, so we shouldn't inline their constructors. if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) if (!Target || !isa<DeclRegion>(Target)) return false; break; } case CE_CXXDestructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return false; // Inlining destructors requires building the CFG correctly. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; const CXXDestructorCall &Dtor = cast<CXXDestructorCall>(Call); // FIXME: We don't handle constructors or destructors for arrays properly. const MemRegion *Target = Dtor.getCXXThisVal().getAsRegion(); if (Target && isa<ElementRegion>(Target)) return false; break; } case CE_CXXAllocator: // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return false; case CE_Block: { const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion(); assert(BR && "If we have the block definition we should have its region"); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); ParentOfCallee = BlockCtx->getBlockInvocationContext(CallerSFC, cast<BlockDecl>(D), BR); break; } case CE_ObjCMessage: if (!(getAnalysisManager().options.IPAMode == DynamicDispatch || getAnalysisManager().options.IPAMode == DynamicDispatchBifurcate)) return false; break; } if (!shouldInlineDecl(D, Pred)) return false; if (!ParentOfCallee) ParentOfCallee = CallerSFC; // This may be NULL, but that's fine. const Expr *CallE = Call.getOriginExpr(); // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = CalleeADC->getStackFrame(ParentOfCallee, CallE, currBldrCtx->getBlock(), currStmtIdx); CallEnter Loc(CallE, CalleeSFC, CurLC); // Construct a new state which contains the mapping from actual to // formal arguments. State = State->enterStackFrame(Call, CalleeSFC); bool isNew; if (ExplodedNode *N = G.getNode(Loc, State, false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) Engine.getWorkList()->enqueue(N); } // If we decided to inline the call, the successor has been manually // added onto the work list so remove it from the node builder. Bldr.takeNodes(Pred); NumInlinedCalls++; // Mark the decl as visited. if (VisitedCallees) VisitedCallees->insert(D); return true; }
bool ExprEngine::inlineCall(ExplodedNodeSet &Dst, const CallEvent &Call, ExplodedNode *Pred) { if (!getAnalysisManager().shouldInlineCall()) return false; const StackFrameContext *CallerSFC = Pred->getLocationContext()->getCurrentStackFrame(); const Decl *D = Call.getDecl(); const LocationContext *ParentOfCallee = 0; switch (Call.getKind()) { case CE_Function: case CE_CXXMember: // These are always at least possible to inline. break; case CE_CXXMemberOperator: // FIXME: This should be possible to inline, but // RegionStore::enterStackFrame isn't smart enough to handle the first // argument being 'this'. The correct solution is to use CallEvent in // enterStackFrame as well. return false; case CE_CXXConstructor: // Do not inline constructors until we can model destructors. // This is unfortunate, but basically necessary for smart pointers and such. return false; case CE_CXXAllocator: // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return false; case CE_Block: { const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion(); if (!BR) return false; D = BR->getDecl(); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); ParentOfCallee = BlockCtx->getBlockInvocationContext(CallerSFC, cast<BlockDecl>(D), BR); break; } case CE_ObjCMessage: case CE_ObjCPropertyAccess: // These always use dynamic dispatch; enabling inlining means assuming // that a particular method will be called at runtime. return false; } if (!D || !shouldInlineDecl(D, Pred)) return false; if (!ParentOfCallee) ParentOfCallee = CallerSFC; const Expr *CallE = Call.getOriginExpr(); assert(CallE && "It is not yet possible to have calls without statements"); // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = CalleeADC->getStackFrame(ParentOfCallee, CallE, currentBuilderContext->getBlock(), currentStmtIdx); CallEnter Loc(CallE, CalleeSFC, Pred->getLocationContext()); bool isNew; if (ExplodedNode *N = G.getNode(Loc, Pred->getState(), false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) Engine.getWorkList()->enqueue(N); } return true; }
bool ExprEngine::inlineCall(ExplodedNodeSet &Dst, const CallEvent &Call, ExplodedNode *Pred) { if (!getAnalysisManager().shouldInlineCall()) return false; bool IsDynamicDispatch; const Decl *D = Call.getDefinition(IsDynamicDispatch); if (!D || IsDynamicDispatch) return false; const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); const LocationContext *ParentOfCallee = 0; switch (Call.getKind()) { case CE_Function: case CE_CXXMember: case CE_CXXMemberOperator: // These are always at least possible to inline. break; case CE_CXXConstructor: case CE_CXXDestructor: // Do not inline constructors until we can really model destructors. // This is unfortunate, but basically necessary for smart pointers and such. return false; case CE_CXXAllocator: // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return false; case CE_Block: { const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion(); assert(BR && "If we have the block definition we should have its region"); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); ParentOfCallee = BlockCtx->getBlockInvocationContext(CallerSFC, cast<BlockDecl>(D), BR); break; } case CE_ObjCMessage: case CE_ObjCPropertyAccess: // These always use dynamic dispatch; enabling inlining means assuming // that a particular method will be called at runtime. llvm_unreachable("Dynamic dispatch should be handled above."); } if (!shouldInlineDecl(D, Pred)) return false; if (!ParentOfCallee) ParentOfCallee = CallerSFC; // This may be NULL, but that's fine. const Expr *CallE = Call.getOriginExpr(); // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = CalleeADC->getStackFrame(ParentOfCallee, CallE, currentBuilderContext->getBlock(), currentStmtIdx); CallEnter Loc(CallE, CalleeSFC, CurLC); // Construct a new state which contains the mapping from actual to // formal arguments. ProgramStateRef State = Pred->getState()->enterStackFrame(Call, CalleeSFC); bool isNew; if (ExplodedNode *N = G.getNode(Loc, State, false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) Engine.getWorkList()->enqueue(N); } return true; }
bool ExprEngine::inlineCall(const CallEvent &Call, ExplodedNode *Pred) { if (!getAnalysisManager().shouldInlineCall()) return false; const Decl *D = Call.getRuntimeDefinition(); if (!D) return false; const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); const LocationContext *ParentOfCallee = 0; switch (Call.getKind()) { case CE_Function: case CE_CXXMember: case CE_CXXMemberOperator: // These are always at least possible to inline. break; case CE_CXXConstructor: case CE_CXXDestructor: { // Only inline constructors and destructors if we built the CFGs for them // properly. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddImplicitDtors || !ADC->getCFGBuildOptions().AddInitializers) return false; // FIXME: We don't handle constructors or destructors for arrays properly. const MemRegion *Target = Call.getCXXThisVal().getAsRegion(); if (Target && isa<ElementRegion>(Target)) return false; // FIXME: This is a hack. We don't handle temporary destructors // right now, so we shouldn't inline their constructors. if (const CXXConstructorCall *Ctor = dyn_cast<CXXConstructorCall>(&Call)) { const CXXConstructExpr *CtorExpr = Ctor->getOriginExpr(); if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) if (!Target || !isa<DeclRegion>(Target)) return false; } break; } case CE_CXXAllocator: // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return false; case CE_Block: { const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion(); assert(BR && "If we have the block definition we should have its region"); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); ParentOfCallee = BlockCtx->getBlockInvocationContext(CallerSFC, cast<BlockDecl>(D), BR); break; } case CE_ObjCMessage: break; } if (!shouldInlineDecl(D, Pred)) return false; if (!ParentOfCallee) ParentOfCallee = CallerSFC; // This may be NULL, but that's fine. const Expr *CallE = Call.getOriginExpr(); // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = CalleeADC->getStackFrame(ParentOfCallee, CallE, currentBuilderContext->getBlock(), currentStmtIdx); CallEnter Loc(CallE, CalleeSFC, CurLC); // Construct a new state which contains the mapping from actual to // formal arguments. ProgramStateRef State = Pred->getState()->enterStackFrame(Call, CalleeSFC); bool isNew; if (ExplodedNode *N = G.getNode(Loc, State, false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) Engine.getWorkList()->enqueue(N); } return true; }